Capítol 3 · Part II · L'ofici
Configuració de Postext
Referència completa de totes les opcions de configuració de composició de Postext
En poques paraules
Aquesta pàgina reuneix tots els paràmetres que decideixen l'aspecte de les teves pàgines. Un sol grup de paràmetres desa totes les decisions: mida de pàgina, nombre de columnes, tipografies, cos del text, títols, requadres, colors i molt més. Només escrius els paràmetres que vols canviar, perquè cadascun parteix d'un valor raonable. És una pàgina de consulta, així que pots anar directament a la part que necessites. Les últimes seccions ensenyen als programadors a crear pàgines web, fitxers PDF i llibres electrònics EPUB des del codi.
Cada decisió de composició a Postext la controla un únic objecte de configuració.
PostextConfig controla les dimensions de pàgina, la disposició de columnes, la tipografia del text de cos, els estils d'encapçalament, la llengua del document (locale) i més. Totes les propietats són opcionals — Postext inclou valors per defecte assenyats, inspirats en la tipografia tradicional de llibres. Només cal que especifiquis el que vulguis canviar.
import { buildDocument } from 'postext';
const document = buildDocument(content, {
page: { sizePreset: '21x28', dpi: 300 },
layout: { layoutType: 'double', gutterWidth: { value: 0.5, unit: 'cm' } },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt sobreescriu el valor per defecte de 8 pt
headings: { fontFamily: 'Open Sans' },
});Per a una visió general de com el motor processa aquesta configuració, consulta la pàgina d'Arquitectura.
#Índex
Aquesta referència és llarga. Aquests són els blocs principals:
- Pàgina — mida, marges, retícula de base, marques de tall, enquadernació.
- Disposició — nombre de columnes, espai entre columnes, filets, escriptura vertical.
- Capçaleres i peus — elements de text i de línia per pàgina amb marcadors, paritat i alineació.
- Text de cos — tipografia, partició de mots, vídues i òrfenes.
- Llengua del document — el
localede primer nivell: alternativa per a la partició de mots, tipus de recurs integrats i cadenes de continuació de taules i d'avisos partits, llengua etiquetada en un PDF accessible; les xifres (numerals) i la direcció del text (direction) que se'n desprenen; i les llengües i escriptures que Postext compon. La guia dels llibres de dreta a esquerra és Composició àrab. - Tipografia de l'Àsia oriental —
cjk: les convencions regionals i el tall de línia del text en xinès, japonès i coreà, com s'eixamplen les seves línies justificades, les amplades de la puntuació i la puntuació penjada, l'espai entre xinès i llatí, la retícula de caràcters, els números que van drets en text vertical, i les marques, el ruby i les notes warichu. La guia de tot plegat és Composició xinesa. - Encapçalaments — valors generals i sobreescriptures H1–H6.
- Llistes no ordenades i Llistes ordenades — vinyetes, numeració, imbricació.
- Matemàtiques — renderització LaTeX, escala, color, marges.
- Tipus de recurs — numeració tipada per a figures, taules i tipus personalitzats.
- Estil de taules (amb estils de taula amb nom) i Estil de peus de recurs — tipografia i decoració de les taules-recurs i els seus peus.
- Estil de diagrames — recoloració a una sola tinta dels diagrames SVG incrustats per a impressió amb tinta plana.
- Estils de paràgraf — estils amb nom per a contenidors
:::paragraphs: bibliografies, glossaris, notes. - Estils d'avís — notes, consells i objectius en caixa per a contenidors
:::callout. - Parts — pàgines separadores de part per a contenidors
:::part: paritat, àrea de cos, disseny d'obertura, tipografia del cos. - Estils d'encapçalament — estils amb nom per a encapçalaments
{style="…"}: capítols sense numerar, preliminars amb capçaleres pròpies, geometria i paleta. - Sumari — el que imprimeix
:::toc: tipografia de les entrades, línies de punts, números de pàgina, línies d'autors, files de part. - Índex analític — el que imprimeix
:::index: tipografia de les entrades, sagnats, separadors i intervals de pàgines, grups per inicial, llengua d'ordenació. - Unitats i colors + Paleta de colors —
Dimension,ColorValue, colors amb nom. - Fonts personalitzades — declarar famílies tipogràfiques pujades per l'usuari al costat de Google Fonts.
- Visor HTML — amplada objectiu de columna i talls per al backend HTML.
- Generació de PDF (configuració) — outlines, sortida accessible (etiquetada) i espai de color forçat per al backend PDF.
- Visor Folio (configuració) — paper, enquadernació, superfície i llum del visor del llibre en 3D.
- Depuració — superposicions visuals i avisos d'autoria per a l'editor.
- Ús programàtic —
buildDocument, els avisos que registra, resolvers, memòries cau. - Executar la composició en un Web Worker — builds fora del fil principal amb cancel·lació.
- Integrar el visor HTML i Generació de PDF — receptes completes.
- Llibres EPUB — fitxers EPUB 3 de maquetació fixa i fluida a partir de la mateixa maquetació, i com comprovar-los amb EPUBCheck.
#Pàgina
La propietat page controla les dimensions físiques i l'aparença de la pàgina.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
sizePreset | PageSizePreset | '17x24' | Mida de pàgina predefinida. Estableix 'custom' per fer servir amplada/alçada explícites. |
width | Dimension | 17 cm | Amplada de pàgina. Es pren de sizePreset quan s'omet; un valor explícit sempre preval (fes servir sizePreset: 'custom' per a mides totalment personalitzades). |
height | Dimension | 24 cm | Alçada de pàgina. Es pren de sizePreset quan s'omet; un valor explícit sempre preval. |
margins | PageMargins | 2 cm tots els costats | Espai entre la vora de la pàgina i l'àrea de contingut. Cada costat (superior, inferior, esquerre, dret) es configura independentment. Amb mirror: true els marges són marges de pàgines enfrontades: left és el marge interior (del llom) i right l'exterior; les pàgines senars (la pàgina 1 és senar) els mantenen tal qual i les parelles els intercanvien, de manera que l'àrea de contingut — i amb ella les columnes, les bandes de flotants, els contenidors de capçalera/peu i les bandes d'obertura — es desplaça al llarg del plec. Per defecte false. Mira més avall. |
backgroundColor | ColorValue | transparent | Color de fons de la pàgina. |
dpi | number | 300 | Punts per polzada. Afecta com es converteixen les unitats físiques (cm, mm, in) a píxels. |
cutLines | CutLinesConfig | desactivat | Mostrar marques de tall a les cantonades de la pàgina per a impressió. En activar-ho, el llenç s'expandeix per incloure l'àrea de sang i les marques de tall. Mira més avall. |
baselineGrid | BaselineGridConfig | desactivat | Dibuixar la retícula de base sobre les pàgines, per comprovar el ritme vertical. La maquetació s'ajusta a la retícula tant si es dibuixa com si no. Mira més avall. |
binding | 'auto' | 'left' | 'right' | 'auto' | La vora per la qual s'enquaderna el llibre. 'auto' és 'right' quan layout.writingMode és 'vertical-rl' o el document va de dreta a esquerra (direction), i 'left' en cas contrari. Un llibre enquadernat per la dreta comença en una pàgina esquerra i emmiralla els marges a l'inrevés. Val per a tot el llibre: el layout propi d'un estil de títol no el canvia. Mira Enquadernació. |
#Marges simètrics (mirall)
Els llibres es llegeixen per plecs, i el marge interior sol ser diferent de l'exterior. margins.mirror converteix els quatre marges en marges de pàgines enfrontades:
{
"page": {
"margins": {
"top": { "value": 2, "unit": "cm" },
"bottom": { "value": 2.5, "unit": "cm" },
"left": { "value": 2.2, "unit": "cm" },
"right": { "value": 1.4, "unit": "cm" },
"mirror": true
}
}
}Amb aquesta configuració tota pàgina senar té 2,2 cm de marge a l'esquerra (el llom) i 1,4 cm a la dreta (el tall davanter); tota pàgina parella té 1,4 cm a l'esquerra (el tall davanter) i 2,2 cm a la dreta (el llom). Cada pàgina composta porta el seu propi contentArea a la VDTPage, així que tot el que se'n deriva — columnes, bandes de flotants a tota l'amplada, contenidors de capçalera i peu, i bandes d'obertura amb span: 'page' — segueix automàticament la geometria emmirallada. Els marcs de pàgina i de sang que fan servir els elements de disseny ancorats a 'page' / 'bleed' no se'n veuen afectats: descriuen el plec físic, no els marges.
#Enquadernació
«Els documents xinesos compostos en vertical s'enquadernen pel costat dret, i els compostos en horitzontal per l'esquerre» (clreq §7.1.1.1). page.binding: 'right' compon un llibre per a la vora dreta:
- La pàgina 1 continua sent senar i continua sent la pàgina senar del plec (recto), així que
breakBefore.parity,:::pagebreak{parity}, els elements de disseny ambparityi el recompte de pàgines conserven el seu sentit. El que canvia és el costat on queda la senar: la pàgina esquerra del plec. Un capítol que comença en senar comença en una pàgina esquerra (clreq §7.1.3.3). - Amb
margins.mirror,leftcontinua sent el marge interior, però són les pàgines senars les que el canvien de costat: la pàgina 1 té el marge interior a la seva dreta i la 2 a la seva esquerra. La columna lateral deoneAndHalfen'outer'/'inner', el flotant girat que es recolza al llom, els marges de les pàgines de part i les icones de cantonada'outer'/'inner'de les caixes segueixen la mateixa regla. - El document ho indica (
VDTDocument.binding: 'right'), de manera que qui el mostra no ha de llegir la configuració: el Sandbox ensenya els plecs com[3 | 2], amb la pàgina 1 sola a l'esquerra del llom, i el seu visor HTML ordena les pàgines de dreta a esquerra, s'obre per l'extrem dret i la fletxa esquerra passa a la pàgina següent.renderToHtmlen mode múltiple ordena la fila de dreta a esquerra. - El PDF porta
/ViewerPreferences << /Direction /R2L >>i/PageLayout /TwoPageRight(la pàgina 1 sola i després per parells), etiquetat o no. Acrobat i Foxit els respecten; el visor integrat de Chrome no fa cas de cap dels dos.
Els folis i les capçaleres no canvien de costat per si sols: una plantilla que posa el número de pàgina a la cantonada exterior necessita els seus elements de pàgina parella i senar preparats per a la vora dreta (els elements admeten parity).
Un llibre escrit de dreta a esquerra (àrab, persa, hebreu…) també s'enquaderna per la dreta: 'auto' dona la vora dreta quan la direction del document resulta 'rtl'. Un llibre així emmiralla a més tot el seu flux: la primera columna és la de la dreta, i els sagnats, els marcadors de llista, els flotants i les notes queden a la dreta; les ranures de capçalera i peu continuen sent físiques. Vegeu Composició àrab.
#Mides de pàgina predefinides
| Preset | Amplada | Alçada | Ús habitual |
|---|---|---|---|
'11x17' | 11 cm | 17 cm | Llibres de butxaca |
'12x19' | 12 cm | 19 cm | Format rústica estàndard |
'17x24' | 17 cm | 24 cm | Llibres tècnics, manuals |
'21x28' | 21 cm | 28 cm | Revistes, informes (proper a A4) |
#Retícula de base
La retícula de base és el ritme del text de cos: línies separades per l'interlineat del cos, comptades des de la vora superior de l'àrea de contingut. La maquetació la fa servir tant si es dibuixa com si no: els encapçalaments, els finals de llista, els requadres, les figures i les fórmules en bloc tornen el text a la retícula (llevat que el seu propi snapToGrid estigui desactivat), de manera que les línies de columnes veïnes queden alineades. enabled només dibuixa les línies, al llenç, al PDF i a les vistes del Sandbox, per comprovar aquest ritme; activar-la o desactivar-la no mou res. Les línies cobreixen només el text real de la pàgina — des de la primera línia de text fins a l'última — de manera que les bandes de flotants, les pàgines en blanc per paritat i l'espai sobrant al final no mostren retícula.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | false | Si es dibuixen les línies de la retícula (llenç i PDF). Només el dibuix: la maquetació és la mateixa en tots dos casos. |
color | ColorValue | #cccccc | Color de les línies de la retícula. |
lineWidth | Dimension | 0.5 pt | Gruix de les línies de la retícula. |
page: {
baselineGrid: { enabled: true, color: { hex: '#e0e0e0', model: 'hex' } }
}#Marques de tall
En activar-se, el llenç s'expandeix per incloure una zona de sang i el motor dibuixa marques de tall a cada cantonada per a producció impresa.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | false | Si s'expandeix el llenç amb sang i es dibuixen les marques de tall. |
bleed | Dimension | 3 mm | Àrea extra al voltant de la pàgina que es fa servir com a sang d'impressió. |
markLength | Dimension | 5 mm | Longitud de cada marca de tall. |
markOffset | Dimension | 3 mm | Separació entre la vora de tall i l'inici de cada marca. Una marca mai no comença dins de la sang: si bleed és més gran, la marca comença a la vora de la sang. |
markWidth | Dimension | 0.25 pt | Gruix de les marques de tall. |
color | ColorValue | #000000 | Color de les marques de tall al llenç i als PDF en RGB o en escala de grisos. Un PDF en CMYK les pinta en color de registre (mira més avall). |
El plec creix bleed + markOffset + markLength per cada costat, i la pàgina tallada queda al centre. Cada cantonada del tall porta dues marques de markLength de llarg, cadascuna a la prolongació d'una de les vores que s'hi troben. Una marca comença a markOffset del tall, o a la vora de la sang si bleed és el més gran dels dos, de manera que cap marca no cau sobre una imatge que s'estén fins a la sang. Amb els valors per defecte (3 mm de sang i 3 mm de separació), les marques van de 3 a 8 mm fora del tall, i per fora d'elles queda una franja en blanc de 3 mm al voltant del plec. cropMarkSegments(page, doc.config.page, doc.trimOffset) retorna les vuit marques d'una pàgina en píxels, les mateixes que dibuixen el canvas i el PDF. El tercer argument és la distància a què queda el tall dins del plec, el valor amb què s'escriu el TrimBox del PDF; si falta, es calcula a partir de cutLines de la mateixa manera.
Fora de la sang només s'imprimeixen les marques. Tot el que pinta la pàgina, inclosos els elements de disseny ancorats a 'page' o a 'bleed', es retalla a la caixa de sang al canvas, al PDF i a la sortida HTML, com ho retalla una exportació de maquetació: una banda o una imatge que surt de la sang a propòsit es talla a la vora de la sang, on la guillotina la perdria de tota manera. Fins a postext 1.4 un element així continuava per damunt de les marques fins a la vora del plec.
Al PDF (postext-pdf), la MediaBox de cada pàgina és el plec sencer. La pàgina porta a més una TrimBox, la pàgina tallada, i una BleedBox, el tall més la sang, que llegeixen les eines d'imposició i de preflight. Quan el PDF s'escriu en CMYK (colorSpace: 'cmyk', o pdfGeneration.forceColorSpace amb colorSpace: 'cmyk'), les marques es pinten en color de registre, la separació /All, i s'imprimeixen en totes les planxes. Els PDF en RGB i en escala de grisos les pinten en color.
#Numeració
El bloc page.pageNumbering controla com es formaten les etiquetes de pàgina i on comença el comptador. Defineix únicament el valor per defecte de tot el document — per reiniciar la numeració al mig del document (per exemple, números romans a les pàgines preliminars que passen a decimal des d'1 als capítols), fes servir la directiva :::numbering (consulta Format del document → Directives).
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
format | 'decimal' | 'lower-roman' | 'upper-roman' | 'lower-alpha' | 'upper-alpha', o un estil de l'Àsia oriental | 'decimal' | Estil numèric utilitzat per renderitzar les etiquetes de pàgina. Els estils de l'Àsia oriental ('trad-chinese-informal' numera les pàgines 一, 二, 三) figuren a Grafies dels formats de numeració. |
startAt | number | 1 | Valor numèric assignat a la primera pàgina, independentment del format. format: 'lower-roman', startAt: 1 produeix i, ii, iii, …; format: 'decimal', startAt: 17 produeix 17, 18, 19, …. |
L'etiqueta calculada s'emmagatzema a cada VDTPage com a pageLabel i és el valor que resol el marcador {pageNumber} a capçaleres i peus. Els PDF emeten un arbre /PageLabels de manera que l'indicador de pàgina i la navegació «Vés a la pàgina» de Preview / Acrobat coincideixen exactament amb les etiquetes impreses. Un estil sense codi en PDF (els numerals xinesos, les xifres en cercle i les d'amplada completa) s'escriu pàgina a pàgina, de manera que el lector mostra també 一, 二, 三.
Grafies dels formats de numeració
Tres paràmetres trien un format de numeració, i cadascun va créixer amb la seva pròpia grafia: les etiquetes de pàgina (page.pageNumbering.format i :::numbering{format=…}) diuen lower-roman, les llistes numerades (orderedLists.numberFormat) diuen arabic per al decimal i els tipus de recurs (counterFormat) diuen roman-lower. Tots accepten totes les grafies de la taula, així que un format copiat d'un paràmetre funciona en els altres. Els noms no distingeixen majúscules; les formes d'un sol caràcter sí (i i I són diferents).
| Format | Imprimeix | Grafies acceptades |
|---|---|---|
| Decimal | 1, 2, 3 | decimal, arabic, 1 |
| Romà en minúscules | i, ii, iii | lower-roman, roman-lower, i |
| Romà en majúscules | I, II, III | upper-roman, roman-upper, I |
| Alfabètic en minúscules | a, b, c | lower-alpha, alpha-lower, lower-latin, a |
| Alfabètic en majúscules | A, B, C | upper-alpha, alpha-upper, upper-latin, A |
| Numerals xinesos, simplificat | 一, 十二, 一百零一 | simp-chinese-informal, 一 en un document en xinès simplificat |
| Numerals xinesos, tradicional | 一, 十二, 一萬 | trad-chinese-informal, cjk-ideographic, 一 en un document en xinès tradicional |
| Numerals xinesos financers, simplificat | 壹, 壹拾贰, 壹佰贰拾 | simp-chinese-formal, 壹 en un document en xinès simplificat |
| Numerals xinesos financers, tradicional | 壹, 壹拾貳, 壹佰貳拾 | trad-chinese-formal, 壹 en un document en xinès tradicional |
| Xifres xineses | 一二〇, 二〇二六 | cjk-decimal, 〇 |
| Troncs celestes | 甲, 乙, 丙 … 癸 | cjk-heavenly-stem, 甲 |
| Branques terrestres | 子, 丑, 寅 … 亥 | cjk-earthly-branch, 子 |
| En cercle | ①, ②, ③ … ㊿ | circled-decimal, ① |
| Xifres d'amplada completa | 1, 2, 3 | fullwidth-decimal, 1 |
| Xifres aràbigues orientals | ١, ٢, ٣ … ١٠ | arabic-indic, ١ |
| Xifres perses | ۱, ۲, ۳ … ۱۰ | persian, urdu, ۱ |
| Lletres àrabs, ordre abjad | أ, ب, ج, د, هـ … غ, أأ | abjad, أبجد |
| Lletres àrabs, ordre alfabètic | أ, ب, ت, ث … ي, أأ | hijai, arabic-alpha, arabic-alphabetic, أبتث |
| Numerals abjad | ا, ب … يا (11), غتمو (1446) | arabic-abjad |
| Numerals abjad, valors magribins | ص (60), ض (90), ش (1000) | arabic-abjad-maghrebi, maghrebi-abjad |
Els estils de l'Àsia oriental conserven el seu nom de CSS Counter Styles en els tres paràmetres. Els numerals xinesos informals escriuen 十 del 10 al 19 sense un 一 al davant (十二, però 一百一十), un sol 零 per cada tram de zeros dins del número (一百零一, 一千零五十), i 万 o 萬 per a la desena de miler, 亿 o 億 per als cent milions (一万零一十). Només cjk-decimal fa servir 〇, xifra a xifra, com s'escriuen els anys (二〇二六, GB/T 15835—2011). Els troncs arriben al 10, les branques al 12 i les xifres en cercle al 50; més enllà, el número s'imprimeix en xifres. 一 i 壹 segueixen l'escriptura del locale del document: tradicional per a zh-Hant, zh-TW o zh-HK, simplificada en els altres casos. Algunes edicions antigues escriuen 101 com 一百一, sense 零; Postext no imprimeix aquesta forma.
Els estils àrabs conserven el nom CSS quan n'hi ha (arabic-indic, persian; urdu i maghrebi-abjad, de la nota del W3C Ready-made Counter Styles, es llegeixen com persian i arabic-abjad-maghrebi). arabic manté el sentit de sempre: les xifres europees. arabic-abjad escriu els numerals abjad additius de l'àrab clàssic i de la foliació de manuscrits, el valor més gran primer: 11 és يا, 1446 غتمو, i el nombre de milers va abans de غ (2000 بغ, 1002 غب); a partir de 999 999 el número s'imprimeix en xifres. La nota del W3C fa servir aquest nom per a una sèrie de 28 lletres en què 11 és ك; Postext anomena aquesta sèrie abjad, la que lletreja els elements de llista en ordre abjad (أ، ب، ج، د، هـ), i hijai l'ordre alfabètic (أ، ب، ت، ث). Totes dues escriuen la primera lletra amb la seva hamza (أ) i una he que quedaria aïllada com هـ, amb tatweel, perquè no es llegeixin com les xifres ١ i ٥; passades les 28 lletres es dupliquen, com lower-alpha (أأ, أب). Els valors magribins segueixen la regla mnemotècnica صعفض قرست ثخذ ظغش (ص 60, ض 90); la llista del W3C intercanvia ص i ض. Una sola أ no distingeix les dues sèries de lletres, així que el seu token són les seves quatre primeres lletres: {1:أبجد}, {1:أبتث}. Les xifres del document mateix es trien amb numerals.
Les altres grafies són per a les configuracions que res no comprova en tipus: els presets en JSON i el JavaScript sense tipus. Els tipus de TypeScript continuen anomenant només la grafia pròpia de cada paràmetre —la que escriu el Sandbox i retorna resolveAllConfig, amb els noms de l'Àsia oriental inclosos—, així que un PostextConfig tipat s'hi cenyeix, i una altra grafia necessita una conversió de tipus (cast).
// JavaScript o un preset en JSON (en TypeScript, la grafia pròpia de cada paràmetre)
orderedLists: { numberFormat: 'decimal' }, // igual que 'arabic'
page: { pageNumbering: { format: 'roman-lower' } }, // igual que 'lower-roman'
resourceTypes: [{ id: 'lamina', counterFormat: 'upper-roman', … }], // igual que 'roman-upper'resolveAllConfig porta els formats de llistes i de pàgina a la grafia del seu propi paràmetre —'decimal' es resol com a 'arabic' a orderedLists, 'roman-lower' com a 'lower-roman' a page.pageNumbering— i stripConfigDefaults elimina qualsevol grafia del valor per defecte. Els taulers del Sandbox mostren cadascun dels tres paràmetres en la seva pròpia grafia, sigui quina sigui la que faci servir la configuració. Qualsevol altre valor —roman, 01, una errada— numera en decimal en lloc d'imprimir undefined, i es notifica com a avís de configuració. Les plantilles de numeració dels encapçalaments accepten els mateixos noms després dels dos punts ({1:roman-upper} equival a {1:I}), al costat del seu propi {1:01} amb zero a l'esquerra.
#Disposició
La propietat layout controla com s'organitzen les columnes dins de l'àrea de contingut.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
layoutType | 'single' | 'double' | 'oneAndHalf' | 'double' | Disposició de columnes. Vegeu més avall els detalls de cada tipus. |
gutterWidth | Dimension | 0.75 cm | Espai horitzontal entre columnes. Només s'aplica a les disposicions de diverses columnes. |
sideColumnPercent | number | 33 | Amplada de la columna lateral com a percentatge de l'àrea de contingut. Qualsevol valor que deixi una mica d'amplada a les dues columnes s'usa tal qual; un que no ho faci s'ajusta, i la composició n'avisa (vegeu Tipus de disposició). Només s'aplica a la disposició 'oneAndHalf'. |
sideColumnRole | 'text' | 'floats' | 'text' | Què porta la columna lateral: text corregut (hi flueix després de la columna principal) o només els recursos i avisos col·locats amb span: 'side' — una columna de marge només per a flotants. Només en 'oneAndHalf'. Amb 'text', un paràgraf que continua d'una columna a l'altra es torna a tallar per a l'amplada de la columna on continua; fins a postext 1.4 conservava les línies de la columna on havia començat, i una línia composta per a la columna principal sobresortia de la lateral, que la retallava. |
sideColumnSide | 'right' | 'left' | 'outer' | 'inner' | 'right' | Vora de l'àrea de contingut on se situa la columna lateral. 'outer' / 'inner' segueixen la paritat de la pàgina quan els marges estan emmirallats (la vora exterior d'una pàgina senar és la dreta; la d'una parella, l'esquerra). Només en 'oneAndHalf'. |
columnRule | ColumnRuleConfig | desactivat | Filet vertical opcional traçat entre columnes. Vegeu més avall. |
fitFiguresToPage | boolean | false | Redueix una figura (mapa de bits o SVG) la imatge, el peu i la nota de la qual quedarien més alts que l'àrea de contingut fins que hi càpiguen, i compon més petita una figura en línia que per poc no cap en el que queda de la seva columna (fins a la meitat de la seva amplada; el peu conserva la mida de la columna) perquè continuï al costat del seu text. La imatge reduïda se situa en el seu buit segons placement.align. El visor HTML l'activa, perquè les seves pàgines són tan altes com la pantalla; les pàgines impreses es dimensionen per a les seves figures. |
hugClosingFloats | boolean | true | A la pàgina de tancament d'un capítol (i del document), les figures i taules a l'amplada de la pàgina que queden sota l'última banda de text pugen fins a quedar a un buit de flotant sota seu, apilades en el seu ordre: allà res no les segueix. Amb false es queden on les ha posat la seva col·locació, de manera que un flotant position: 'bottom' acaba al peu de la pàgina de tancament com en qualsevol altra — per exemple, en una fitxa tècnica el contorn de la qual acaba a la mateixa alçada a totes les pàgines. A les pàgines amb columna lateral no es mouen mai. |
inlineResourceGap | 'around' | 'above' | 'around' | On deixa un recurs en línia (placement.position: 'here', inserit amb ::resource) el buit dels flotants, d'una línia. 'around' el conserva damunt i sota el recurs, i el text que segueix torna a la retícula de base sota aquest buit; un encapçalament, una llista, un requadre o un altre recurs en línia just després comparteix el buit de sota amb el seu propi espai superior, i s'aplica el més gran dels dos. 'above' el conserva només a sobre: el text que segueix el recurs continua a la línia següent de la retícula, per prop que quedi —entre res i una línia—, com fins a postext 1.4. Les configuracions desades per versions anteriors els capítols de les quals insereixen un recurs es llegeixen amb 'above', perquè les seves pàgines no es moguin (vegeu Paquets escrits per postext 1.4 o anterior). Una configuració escrita en codi per a la 1.4 conserva l'espai antic si hi posa ella mateixa 'above', o si passa per pinLegacyInlineGap, de postext/bundle. |
inlineResourceGapInBoxes | boolean | true | Si un recurs en línia dins d'un requadre (:::callout) conserva el buit que fixa inlineResourceGap, una línia del text del mateix requadre: damunt del recurs i, amb 'around', també a sota, i s'aplica el més gran entre aquest buit i l'espai propi del bloc següent. Al principi o al final del requadre, o d'un fragment d'un requadre partit, ja el separa el farciment i no s'hi afegeix buit. Amb false el recurs queda just sota el text anterior i el text següent just sota el recurs, com fins a postext 1.4. Les configuracions desades per versions anteriors els capítols de les quals insereixen un recurs dins d'un requadre es llegeixen amb false (vegeu Paquets escrits per postext 1.4 o anterior); en codi, pinLegacyBoxResourceGap, de postext/bundle, fa el mateix. |
boxChildSplitMinLines | number | 2 | Mínim de línies d'un paràgraf o d'un element de llista que deixa a cada costat un tall dins seu quan una caixa es parteix (splitMinLines, a Estils d'avís, continua comptant totes les línies de la caixa a cada costat del tall). Un nombre enter, com a mínim 1. Amb el valor per defecte, un tall no deixa mai una línia sola d'un paràgraf o element al peu d'una columna ni al principi de la següent; si el splitMinLines de l'estil de la caixa és més petit, mana aquest. Amb 1 un tall pot deixar una línia del paràgraf o element a un costat, com fins a postext 1.4. Les configuracions desades per versions anteriors els capítols de les quals tenen algun :::callout es llegeixen amb 1 (vegeu Paquets escrits per postext 1.4 o anterior); en codi, pinLegacyBoxChildCut, de postext/bundle, fa el mateix. |
writingMode | 'horizontal-tb' | 'vertical-rl' | 'horizontal-tb' | Com corren les línies. 'vertical-rl' compon el text xinès o japonès en vertical: els caràcters de dalt a baix i cada línia a l'esquerra de l'anterior. El layout d'un estil de títol l'hereta tret que en fixi un de propi, de manera que un apèndix horitzontal pot seguir un llibre vertical. Vegeu Escriptura vertical. |
#Escriptura vertical
Amb writingMode: 'vertical-rl' una pàgina es compon com una pàgina horitzontal girada un quart de volta en el sentit de les agulles del rellotge. El flux es compon en un marc tan ample com alt és el plec; les seves línies són les columnes del text vertical, que es llegeixen des de la dreta, i tot el que el motor fa amb les línies (talls, justificació, flotants, notes a peu de pàgina, regles de cohesió) funciona en aquest marc. Al plec, per tant:
- Una columna de la disposició és un pis (栏):
layoutType: 'double'dona dos pisos apilats de dalt a baix, que s'omplen des de la cantonada superior dreta;gutterWidthés el buit entre ells i el filet de columna, una línia horitzontal entre tots dos. Els pisos no s'igualen al final d'un capítol (clreq §7.1.3.4): l'equilibri de columnes està desactivat en un document vertical tret que es fixiheadings.balancing.enabled. - El que el flux anomena «dalt» és la vora dreta del plec, on comença la lectura: un flotant superior queda a la dreta de la pàgina, un d'inferior a l'esquerra, una obertura a tota la pàgina és una franja al llarg de la vora dreta i les notes a peu de pàgina cauen a l'extrem esquerre de cada pis. Un element de disseny ancorat a la part superior de la pàgina (un disseny de títol, una caixa fixa) s'ancora a la vora dreta. A
sideColumnSide,'left'és el pis de dalt i'right'el de baix;'outer'i'inner'valen com'right'i'left'. - Els marges del flux són els del plec girats: el marge dret és la part superior del flux i el superior, la seva esquerra.
page.marginsconserven els seus noms al plec. - Els encapçalaments, els folis, les marques de tall i el fons de pàgina continuen al plec, en horitzontal, com descriu clreq per als llibres verticals.
- Les figures i les taules es mantenen dretes. Una figura ocupa l'alçada del seu pis fins on ho permet el seu peu, i com a molt l'amplada de la pàgina; l'amplada que pren és l'espai que ocupa en el flux. El seu peu es compon en horitzontal sota seu, igual que les cel·les d'una taula: tots dos es mesuren com a text horitzontal. Una taula es compon dreta a l'amplada de la pàgina i, si és més alta que el pis, les seves files es reparteixen entre pisos.
placement.aligncol·loca la figura a dalt ('left'), al mig o al peu del seu pis. Unplacement.rotateno s'aplica quan la figura se cita per primer cop en text vertical: la composició n'avisa ambrotateIgnoredVertical. Una secció horitzontal del llibre (un estil de títol ellayoutdel qual fixa'horizontal-tb') gira les seves figures tal com se li demana. - Les imatges d'un disseny (la il·lustració d'una obertura o d'una pàgina de part) també queden dretes. La seva caixa en el flux es dimensiona amb l'amplada i l'alçada de la imatge intercanviades, de manera que
size.widthés el que la imatge ocupa al llarg de la columna, i la seva amplada al plec surt de les seves proporcions. - Caràcters: els hanzi, els kana i les formes d'amplada completa queden drets, a un quadratí cadascun; les paraules llatines i els números s'ajeuen amb la seva amplada horitzontal; la puntuació pren la forma vertical de la font. Els signes de pausa i de final no es giren mai: una font de la Xina continental posa 、。,. a la cantonada superior dreta de la cel·la i !?:; a la seva meitat dreta, i una de Taiwan o Hong Kong els centra (
cjk.region). Els parèntesis i les cometes prenen la seva forma vertical, i “ ” ‘ ’ es llegeixen com a 『』「」 en text de la Xina continental. Els guions llargs, els punts suspensius i la titlla d'ona prenen la forma vertical de la font quan en té (Noto CJK lliga la de — avertjuntament ambfwid: un filet pel centre de la cel·la); si no, es giren amb la tinta centrada en l'eix de la columna. El 破折号 (——) d'un text xinès és un sol filet al llarg de la columna: les seves formes verticals deixarien blanc als dos extrems de cada cel·la, així que cada guió es gira amb la línia i s'estira com en el text horitzontal (vegeu Amplades de la puntuació). Un número de dues xifres com a molt queda dret en una sola cel·la, excepte dins d'una frase llatina, les paraules de la qual segueix (vegeu Números en text vertical). El punt volat (·) ocupa mitja cel·la en text de la Xina continental i una cel·la sencera en el de Taiwan i Hong Kong. Els signes que Unicode posa drets (× © ± § ℃ ① i semblants) ocupen una cel·la pròpia, també dins d'un número:3×4són el 3 i el 4 ajaguts amb el × dret entre ells. Un apòstrof o un punt volat entre dues lletres d'una paraula llatina (don’t,l·l) continua a la paraula, ajagut. Un paràgraf llatí dins d'un flux vertical segueix les mateixes regles. Les fórmules en línia, les fitxes i les mostres de color s'ajeuen amb la línia. - Cada caràcter es mesura tal com es pinta: una cel·la avança la seva cel·la al llarg de la línia, un tram ajagut la seva amplada horitzontal. El text que queda horitzontal al plec (titolets, folis, peus, cel·les de taula) es mesura en horitzontal.
- Les amplades de la puntuació, la puntuació penjada i l'espai entre xinès i llatí s'apliquen al llarg de la línia vertical igual que en l'horitzontal. El blanc que va davant d'un dibuix queda a sobre i el que va darrere, a sota: un 、 kaiming ocupa mitja cel·la,
」「es comprimeixen a cel·la i mitja, un parèntesi d'obertura retallat al cap d'una línia comença mitja cel·la més amunt, un 。 penjat queda sota el peu de la seva línia, i l'espai entre xinès i llatí és un quart de quadratí de columna damunt i sota d'una paraula ajaguda.:;?!conserven la cel·la sencera en text vertical a totes les regions. - La retícula de caràcters compta els caràcters al llarg de la línia i les línies a l'amplada de la pàgina:
charsPerLinefixa el que mesura un pis,linesPerPagequantes línies porta una pàgina, ilayoutType: 'double'dona dos pisos de caràcters sencers amb un espai entre columnes de quadratins sencers entre ells.
Per a qui llegeix la composició: una pàgina vertical porta VDTPage.flow. El seu contentArea, les seves columnes, blocs, línies, flotants, àrees de notes, banda d'obertura i superposicions de disseny dels blocs són en coordenades del flux; width, height, header i footer són al plec. flowToPage, pageToFlow, flowRectToPage i pageRectToFlow passen de les unes a les altres, i verticalOrientation(char, region) diu com queda un caràcter. flow.centralBaselines dona, per família tipogràfica, l'eix sobre el qual la composició ha centrat els caràcters drets: el centre de la tinta de 中, el traç llarg del qual ocupa l'alçada del quadratí (0,38 quadratins sobre la línia de base a Noto Serif i Noto Sans, SC i TC per igual), i que tenen totes les fonts xineses, japoneses i coreanes. Cada línia es centra en aquest eix: la línia de base d'una línia vertical queda, des de la vora de la seva caixa, a la meitat del seu interlineat més la línia de base central de la família (la d'una línia horitzontal, a 0,8 de l'interlineat), així que una columna de caràcters queda al centre del seu pas i un filet traçat entre dues columnes a un pas sencer cau a mig camí entre elles. Un text de disseny vertical es compon igual en les seves pròpies línies. El canvas pinta la pàgina vertical pel seu compte; per pintar la puntuació amb les formes verticals pròpies de la font, un amfitrió al navegador carrega, una vegada per família, una font bessona amb aquestes formes activades: loadVerticalAlternates(family, faces), on faces són els orígens de la família (URL o bytes) i els seus descriptors. La bessona només es queda si el navegador aplica la funció al text del canvas (Chrome 140 i posteriors): dibuixa 「(《 amb la bessona i amb una còpia de les mateixes fonts carregada sense la funció, en el pes i l'estil de les fonts rebudes, i es queda amb la bessona quan la tinta és diferent. Una crida posterior amb altres fonts d'una família la bessona de la qual ja és en ús (la negreta després de la rodona) les hi afegeix. El Sandbox ho fa amb totes les famílies d'un document vertical. Sense bessona, els parèntesis es giren sobre el centre del seu quadratí i els signes de pausa i de final de la Xina continental es desplacen dins de la seva cel·la allà on els posen les formes verticals de la font: 、。,. a la cantonada superior dreta (a menys de 0,07 quadratins de les formes verticals de Noto Serif SC) i !?:; mig quadratí a la dreta i una mica cap amunt (a menys de 0,02). El PDF i l'HTML componen la mateixa pàgina: vegeu Text vertical al PDF i la taula d'En què es diferencia la sortida HTML del canvas i del PDF. Comparats cel·la a cel·la amb HarfBuzz (composició vertical amb vert) a Noto Serif TC i SC, tots els caràcters de 「賈雨村」云云,宜乎?故曰!;:、。“引”‘單’…… queden a menys de 0,02 quadratins d'aquest al canvas i al PDF, i a menys de 0,05 a l'HTML (Chrome centra un glif girat al mig de l'ascendent i el descendent de la font, que a Noto queda 0,05 quadratins per sobre del centre del quadratí). flow.dashAdvances dona, per família, l'avanç horitzontal en quadratins de cada guió que la pàgina estira fins a omplir la seva cel·la (— – ― ⸺ ⸻ -), per a qui pinta sense mètriques de font pròpies: l'HTML estira el guió amb aquest valor, com el canvas i el PDF ho fan amb les seves. Les capçaleres i els folis també poden anar en vertical: vegeu Elements de text verticals.
#Filet de columna
Dibuixa una línia vertical fina a l'espai entre columnes per separar visualment les columnes.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | false | Si es dibuixa el filet de columna. |
color | ColorValue | #cccccc | Color del filet. |
lineWidth | Dimension | 0.5 pt | Gruix del filet. |
Sota un encapçalament de pàgina completa (span: 'page'), el filet comença on comença el text de les columnes, sota la banda de l'encapçalament, tant si la pinta l'obertura per defecte com un disseny propi. Fins a postext 1.4 baixava des del principi de la caixa de text, travessant la banda.
Un estil d'encapçalament pot fixar el seu propi filet al seu layout (vegeu Estils d'encapçalament), i les pàgines de la seva secció dibuixen aquest. Un camp que l'estil deixa sense fixar pren el valor del document, així que una secció que només canvia les columnes conserva el filet del document. Fins a postext 1.4 el filet d'un estil no es dibuixava mai: totes les pàgines dibuixaven el del document.
#Tipus de disposició
-
'single'— Una columna que ocupa tota l'amplada del contingut. Ideal per a pàgines estretes o contingut amb paràgrafs llargs. -
'double'— Dues columnes d'igual amplada. La disposició editorial clàssica — manté l'amplada de línia en el rang òptim de 40–50 caràcters per a una lectura còmoda. -
'oneAndHalf'— Una disposició asimètrica amb una columna principal i una columna lateral més estreta. La columna lateral (controlada persideColumnPercent) és ideal per a notes al marge, figures petites o contingut complementari. Els valors entre el 25–40% funcionen bé, i un canal estret per numerar línies o posar marques al marge ocupa al voltant del 10–15%. La columna lateral ocupa elsideColumnPercent% de l'amplada de l'àrea de contingut i la principal, el que queda després de l'espai entre columnes — així, amb el 50% la lateral és un espai entre columnes més ampla que la principal. Qualsevol valor es compon tal qual mentre les dues columnes conservin com a mínim l'1% de l'amplada de l'àrea de contingut; un que en deixaria alguna més estreta — 0 o menys, o un de tan gran que la principal desapareix després de l'espai entre columnes — s'ajusta al valor més proper que respecti totes dues, i un valor que no és un nombre pren el valor per defecte, 33. ElsconfigWarningsdel document porten llavors{ kind: 'sideColumnPercentClamped', path: 'layout.sideColumnPercent', value, used }— la ruta dellayoutpropi d'un estil d'encapçalament anomena l'estil, com aheadingStyles[2].layout.sideColumnPercent, i es mesura amb els marges d'aquest estil —, que el Sandbox mostra al tauler Revisió;collectConfigWarnings(config)retorna la mateixa llista sense compondre res. Una disposició que no és'oneAndHalf'no llegeix el valor, i tampoc no n'avisa. AmbsideColumnRole: 'floats'el text corregut no entra mai a la columna lateral: es converteix en un canal per a les figures, taules i requadres col·locats ambspan: 'side'. Una figura o taula lateral s'apila des del cap del canal a la pàgina que la cita per primer cop: la figura marginal d'un llibre de text queda a dalt de tot de la seva pàgina encara que el text la citi més avall, i si no cap en el que queda del canal espera al de la pàgina següent. Un requadre lateral s'apila al costat del text que interromp; si la resta del canal no el pot allotjar allà, puja a la posició més baixa on hi cap (el seu peu al peu del canal) o espera a la pàgina següent. Quan el text que segueix la tanca d'un requadre continua a la pàgina següent (la columna és plena, o les regles de tall porten aquest text més enllà), el requadre es queda a la seva tanca, al costat del text anterior; un estil ambsideAtColumnEnd: 'after'el posa en canvi a l'altura de la primera línia del text que segueix la tanca, al canal de la pàgina següent, com necessiten els números de línia i els titolets marginals escrits abans de la seva línia. Un element del disseny d'un encapçalament que cau al canal —un numeral de capítol ancorat al marge exterior— també queda fora de la pila (vegeu Alçada reservada). Els flotants i requadresspan: 'page'continuen creuant totes dues columnes, i un flotant de columna ambplacement.captionSideposa el seu peu al canal, a l'altura de la figura. Combinada amb marges emmirallats isideColumnSide: 'outer', el canal queda a la vora exterior de cada pàgina — la columna marginal d'un manual.
#Capçaleres i peus
Les propietats header i footer controlen els blocs de capçalera i peu de pàgina. Les capçaleres i els peus es dibuixen dins dels marges de pàgina existents — no reserven espai addicional ni redueixen l'àrea de contingut.
Marc del contenidor. Un element ancorat a 'container' es col·loca a la franja de marge entre el cos i la vora de tall, amb l'amplada de l'àrea de contingut. El contenidor de la capçalera va des de la vora superior de tall fins a la part superior del cos; el del peu, des de la part inferior del cos fins a la vora inferior de tall. Així, els ancoratges top-* de la capçalera i bottom-* del peu es mesuren des de la vora de tall, mentre que els bottom-* de la capçalera i top-* del peu es mesuren des de la vora del cos. El contenidor no inclou mai la zona de sang ni la franja de les marques de tall, de manera que una capçalera o un peu queda a la mateixa posició a la pàgina tallada tant si page.cutLines està activat com si no. Ancora a 'page' (la caixa de tall) o a 'bleed' per sortir de l'amplada de l'àrea de contingut o arribar a la zona de sang.
Els blocs utilitzen el model unificat de ranura de disseny: cada element defineix un placement amb un anchor (al contenidor o a un altre element per #id), un offset opcional i un size opcional. Els camps plans heretats align, marginFromBody, marginFromEdge i width: 'full' continuen acceptant-se com a entrada i es migren automàticament al format nou; a sota es documenta l'equivalència.
Cada bloc conté una llista d'elements de text, línia (rule) i caixa (box). L'ordre de l'array és l'ordre de pintura (el primer element es pinta primer, l'últim es pinta a sobre). Això es compleix siguin quins siguin els ancoratges: un element es pot ancorar a un altre que apareix després a la llista (anchor.to: '#ttl'), de manera que una caixa de fons pot anar primer i col·locar-se respecte al text que es pinta a sobre seu.
Valors per defecte inclosos. Quan header o footer són undefined, postext aplica un valor per defecte raonable en lloc d'un bloc buit:
- Capçalera per defecte:
{title}alineat a la dreta a les pàgines senars,{chapterTitle}alineat a l'esquerra a les pàgines parelles i una línia a tota l'amplada — tots en el color principal de la paleta, Open Sans 8pt/600,marginFromBody16pt(text) /13pt(línia). - Peu per defecte:
{pageNumber}centrat a totes les pàgines en el color principal de la paleta, Open Sans 8pt/600,marginFromBody16pt.
Per renunciar als valors per defecte, estableix header: { elements: [] } (o footer: { elements: [] }). Un array elements buit explícit es conserva com a «sense elements» — només undefined activa els valors per defecte.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
elements | HeaderFooterElement[] | valors per defecte integrats quan és undefined; [] els desactiva | Llista ordenada d'elements de text i línia. |
#Elements de text
Els elements de text renderitzen una plantilla amb substitució de marcadors. Els marcadors fan servir la sintaxi {nombre}; {{ i }} s'emeten com a claus literals.
Els valors per defecte de la taula següent són els d'un element de text que afegeixes tu. La capçalera i el peu integrats descrits a Valors per defecte inclosos són elements ja fets amb els seus propis valors (Open Sans 8pt/600 en el color principal de la paleta), no els valors per defecte de l'element.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
kind | 'text' | — | Discriminador. |
id | string | — | Id estable, únic dins del bloc. Altres elements s'hi ancoren amb anchor.to: '#id'. El sandbox n'assigna un en crear-lo. |
content | string | '' | Plantilla. Admet els marcadors que es llisten més avall, a més de — un atribut escrit a la línia de l'H1 del capítol actual (# Título ). Un atribut absent es resol com a cadena buida sense avís. Un salt de línia —o els dos caràcters \n, escrits a la plantilla o al valor d'un atribut— comença sempre una línia nova, sigui quin sigui l'overflow. El text que un marcador copia del document (un títol, un camp del frontmatter) s'imprimeix tal qual: aquí només un salt de línia real, com el salt d'un títol, comença una línia nova. |
align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'center' | Alineació horitzontal de les línies dins de la caixa de l'element. 'justify' eixampla els espais entre paraules de cada línia ajustada, excepte l'última de cada paràgraf, fins que la línia omple la caixa; les línies que acompanyen una caplletra omplen l'espai que queda al seu costat. Amb hyphenate, una paraula que no cap en el que queda d'una línia justificada també es parteix per una síl·laba per omplir-la. L'última línia d'un paràgraf, una línia sense espais per eixamplar i un text que no s'ajusta s'alineen a l'esquerra. El text justificat es compon paràgraf a paràgraf, de manera que diverses línies en blanc entre paràgrafs compten com una. El llenç i el PDF col·loquen cada paraula on la va posar la maquetació; l'HTML eixampla els espais amb word-spacing. 'start' i 'end' segueixen la direction del text: la dreta i l'esquerra d'un text de dreta a esquerra. 'left' i 'right' són els costats de la caixa mateixa; en un disseny que es compon en el flux d'una pàgina de dreta a esquerra (una franja d'obertura, el disseny d'un títol) el flux està emmirallat, així que en són l'inici i el final, com al text general. |
direction | 'ltr' | 'rtl' | 'auto' | la del document | Direcció base del text: on van els seus caràcters neutres, l'ordre dels seus trams en una línia i quins costats volen dir 'start' i 'end'. 'auto' llegeix la primera lletra forta del text resolt i, si no n'hi ha, pren la direction del document. Els trams en àrab o hebreu es llegeixen de dreta a esquerra sigui quina sigui la base. Un text que conté una lletra àrab no s'espaia mai (s'ignora tot el seu letterSpacing) i les seves paraules no es tallen en ajustar ni en truncar; una paraula més ampla que la caixa la desborda i s'avisa com a unbreakableWordOverflow. |
parity | 'all' | 'odd' | 'even' | 'all' | En quines pàgines apareix l'element (paritat per número de pàgina: la pàgina 1 és senar). |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | En quins rols de pàgina apareix l'element, combinat amb parity. Després de la col·locació cada pàgina es classifica com a 'blank' (farciment de paritat o separador, o sense contingut), 'part' (pàgina divisòria de part), 'opener' (el seu primer bloc és un encapçalament el nivell del qual abasta la pàgina o força un salt de pàgina abans — la primera pàgina d'un capítol) o 'body' (la resta). pages: 'body' amaga una capçalera corrent a les obertures de capítol; pages: 'opener' mostra un foli només allà. |
fontFamily | string | 'EB Garamond' | Família tipogràfica. |
fontSize | Dimension | 8 pt | Mida de la font. |
fontWeight | number | 400 | Gruix de la font (100–900). |
italic | boolean | false | Si es renderitza en cursiva. |
color | ColorValue | #000000 | Color del text. |
overflow | 'wrap' | 'ellipsis-start' | 'ellipsis-middle' | 'ellipsis-end' | 'clip' | 'ellipsis-end' | Com es gestiona el text que excedeix l'amplada disponible de l'element. 'wrap' divideix el text en diverses línies; les variants d'el·lipsi mantenen cada línia en una de sola i la trunquen amb … al principi, al centre o al final; 'clip' retalla de manera estricta al rectangle de l'element sense inserir cap caràcter. Els salts de línia del contingut valen en tots els modes: l'el·lipsi i el retall s'apliquen a cada línia per separat. Un element que omet overflow rep 'ellipsis-end', així que fes servir 'wrap' per a tot el que pugui ocupar diverses línies (una adreça, un títol llarg). Un text amb dropCap passa a línia nova digui el que digui aquest valor; amb 'clip' les seves línies es continuen tallant a la vora d'una caixa d'alçada fixa. 'ellipsis-end' i 'ellipsis-start' tallen per un límit de paraula —La història de la…, no La història de la na…—, i cap espai ni signe d'enllaç (coma, dos punts, guió llarg, barra, parèntesi d'obertura) no queda al costat de l'el·lipsi. Només es talla una paraula per on calgui quan el límit, suprimits aquests signes, conservaria menys de la meitat del que hi cap: una paraula llarga o un URL (http://exampl…, no http…). Un espai de no separació o un guionet de no separació (U+2011, com en MS‑DOS) no és un límit de paraula; 'ellipsis-middle' talla en qualsevol punt, però suprimeix els espais que l'envolten. Fins a postext 1.4 tots els modes tallaven a l'últim caràcter que hi cabia, espais inclosos. |
verticalAlign | 'top' | 'middle' | 'bottom' | 'middle' | On se situa el text dins d'una caixa més alta que les seves línies — un placement.size.height fix, o una caixa estirada per un veí ancorat. Si les línies són més altes que la caixa (una xifra gran en una caixa d'alçada fixa, un lineHeight estret), sobresurten pel costat que l'alineació deixa lliure, com a l'alineació flex de CSS: 'bottom' manté el peu de la caixa de l'última línia sobre el peu de la caixa i sobresurt per dalt, 'middle' sobresurt el mateix pels dos extrems i 'top' pel peu. Fins a postext 1.4 aquestes línies penjaven sempre de la part superior, fos quina fos l'alineació. La caplletra (dropCap) es mou amb les seves línies (fins a postext 1.4 es quedava a dalt en una caixa on 'middle' o 'bottom' les baixaven). |
lineHeight | number | Dimension | 1.2 | Interlineat de les línies de l'element. Un nombre és un múltiple de fontSize. També s'accepta un Dimension, com s'escriuen tots els altres interlineats de la configuració: em / rem és el mateix múltiple, i una longitud absoluta (pt, mm, px…) és la distància entre línies de base: fixa un interlineat de 15,5 pt sigui quin sigui el cos. Qualsevol altre valor (zero, un nombre negatiu, una dimensió mal formada) pren el valor per defecte. Fins a postext 1.4 un Dimension aquí feia impossible mesurar l'alçada del disseny: una obertura no reservava llavors cap espai, ni tan sols el seu minHeight, i el cos corria per sota del títol. |
letterSpacing | Dimension | 0 | Tracking: espai addicional que avança després de cada caràcter, espais inclosos, exactament com el letter-spacing de CSS. Les mesures creixen amb ell, de manera que una caixa d'amplada automàtica continua ajustada. El tracking que segueix l'últim caràcter d'una línia no compta per a la seva alineació ni per a una caixa d'amplada automàtica, així que un títol espaiat i centrat queda centrat sobre les seves lletres, un d'alineat a la dreta acaba a la vora i l'última lletra d'una línia justificada arriba a la vora (fins a postext 1.4 quedaven mig espai de tracking, o un de sencer, a l'esquerra, i un element ancorat a la dreta d'un d'espaiat quedava un espai de tracking més lluny). Un valor negatiu estreny les lletres —un títol de 36 pt sol portar { value: -0.3, unit: 'pt' }— i les mesures es redueixen igual; canvas, HTML i PDF el pinten igual (fins a postext 1.4 un valor negatiu es componia com a 0, sense avís). |
textTransform | 'none' | 'uppercase' | 'none' | Transformació de majúscules aplicada al text resolt, marcadors inclosos — el títol d'una part en versals a l'índex. |
box | ElementBoxStyle | — | Fons i vora opcionals dibuixats darrere del text: backgroundColor, borderColor, borderWidth, borderRadius i un padding per costat que amplia la caixa més enllà del text (vegeu Elements de caixa per als camps). |
dropCap | { lines, fontFamily, fontWeight, fontSize, color, gap } | — | Caplletra: la primera lletra, gran, al costat de les primeres lines línies (2 per defecte), amb la seva pròpia font, gruix i color, i a gap del text. La lletra s'assenta a la línia de base de l'última línia que abasta, i fontSize val per defecte la mida que enrasa la seva part alta amb les majúscules de la primera línia: el cos del text més lines − 1 interlineats, prenent l'alçada de les majúscules com a 0,72 del cos (una font amb majúscules molt més altes o més baixes demana el seu propi fontSize). Un text amb caplletra s'ajusta a línies noves sigui quin sigui el seu overflow; amb 'clip' les seves línies es continuen tallant a la vora d'una caixa d'alçada fixa. Un color enllaçat a la paleta segueix les paletes de la part i de la secció, com la resta del disseny. En el disseny d'un encapçalament, la lletra no reserva espai per sota del text: la part de la seva caixa de línia que queda sota la seva línia de base no empeny el cos cap avall. Una lletra que baixa de la seva línia de base (la Q o la J de moltes fonts) pot llavors entrar en l'espai que hi ha sota el disseny: dona a l'encapçalament un marginBottom per a ella. Fins a postext 1.4, la mida per defecte feia la lletra tan alta com totes les caixes de línia que abasta, de manera que sobresortia per damunt de la primera línia; qualsevol overflow diferent de 'wrap' treia la lletra sense avisar; la paleta d'una secció o d'una part no en canviava el color; i una lletra tan profunda com el text del seu costat podia baixar el cos una línia de la retícula. Les configuracions desades abans conserven la mida de la 1.4, escrita com a fontSize (vegeu Paquets escrits per postext 1.4 o anterior). |
paragraphIndent | Dimension | 0 | Sagnat de primera línia de cada paràgraf a partir del segon. Un salt de línia en el contingut —o els dos caràcters \n, en un text que ve d'un atribut— separa paràgrafs; diversos salts seguits compten com un. |
hyphenate | boolean | false | Quan és true i el text passa a línia nova (overflow: 'wrap', o un dropCap, que sempre ho fa), les paraules llargues que encara excedirien l'amplada després d'un salt de línia normal es divideixen en límits sil·làbics (fent servir la llengua de partició de mots activa del document) inserint un guionet tou al tall. En un text justificat (align: 'justify'), una paraula que no cap en el que queda d'una línia també es parteix per la seva última divisió sil·làbica que hi càpiga, per omplir la línia. |
inlineMarks | boolean | false | Llegeix el text resolt —amb els valors dels marcadors— com a Markdown en línia: negrita, cursiva, ^superíndice^, ~subíndice~. Desactivat, els signes de marca s'imprimeixen tal qual. Vegeu Marques en línia i contorns. |
stroke | { width, color?, hollow? } | — | Contorn traçat al voltant de les lletres: width (un Dimension, centrat a les vores dels glifs), color (per defecte, el del text; la caplletra pren el seu propi color) i hollow (true pinta només el contorn). Vegeu Marques en línia i contorns. |
writingMode | 'horizontal-tb' | 'vertical-rl' | 'horizontal-tb' | 'vertical-rl' compon el text de dalt a baix, les línies de dreta a esquerra i els caràcters drets: una capçalera pel marge exterior, un títol vertical al costat d'un capítol horitzontal. Vegeu Elements de text verticals. |
reserve | boolean | true | Només en dissenys d'encapçalament: si l'element compta per a l'alçada que l'encapçalament reserva en el flux del text. false per a la decoració que pot quedar sota el text (un segell al peu de la pàgina, un marc, una banda lateral). Vegeu Alçada reservada. Les capçaleres i peus de pàgina i els dissenys de part l'ignoren. |
marginFromBody | Dimension | 6 pt | Distància absoluta entre la vora de l'element que mira cap al cos i la vora del cos. Independent d'altres elements. Migrat a placement.offset.y. |
marginFromEdge | Dimension | 0 pt | Desplaçament horitzontal respecte a la vora a la qual està alineat. Només s'aplica quan align és 'left' o 'right'. Migrat a placement.offset.x. |
placement | ElementPlacement | derivat d'align + marginFromBody + marginFromEdge | Posicionament avançat (vegeu més avall). Quan es defineix, preval sobre els camps plans heretats. |
Marcadors disponibles:
{pageNumber}— número de pàgina actual (1-indexed).{totalPages}— total de pàgines del document. En un llibre compost capítol a capítol (el Sandbox,buildBundle) cada capítol és un document, de manera que és el recompte del mateix capítol.{bookTotalPages}— total de pàgines del llibre sencer: tots els capítols, pàgines en blanc incloses. En un document compost tot sol coincideix amb{totalPages}. Consulta Recompte de pàgines del llibre més avall.{title},{subtitle},{author},{publishDate}— valors llegits decontent.metadata. Les metadades desconegudes o buides es renderitzen com a cadena buida (i generen un avís al sandbox).{chapterTitle}— text de l'H1 més recent a la pàgina actual o abans. Se salta un H1 l'estil d'encapçalament del qual fixarunningChapter: false(una làmina, un mapa).{chapterTitleAtTop},{chapterNumberAtTop}— títol i número del capítol vigent al cap de la pàgina, que no coincideixen amb{chapterTitle}i{chapterNumber}en una pàgina on un capítol nou comença sota un altre text. Consulta Capítol al cap de pàgina més avall.{partTitle},{partNumber}— títol i número de la part actual (la pàgina:::partmés recent a la pàgina actual o abans; les pàgines en blanc de paritat just abans d'una pàgina de part ja hi pertanyen). Buits abans de la primera part.{firstMark.<clave>},{lastMark.<clave>}— la primera i l'última paraula guia de la pàgina: un títol d'un nivell (h1–h6) o una entrada d'un estil de paràgraf. Consulta Paraules guia més avall.
Recompte de pàgines del llibre
{bookTotalPages} imprimeix el nombre de pàgines del llibre sencer, el que el lector veu a «pàgina 12 de 348». Compta pàgines físiques, les blanques incloses, com {totalPages}, però al llarg de tots els capítols:
- Un document compost tot sol (
buildDocumentsensecontinuation) és el llibre sencer:{bookTotalPages}coincideix amb{totalPages}. buildBundlecompon el llibre, suma les pàgines de tots els capítols i el compon un cop més amb aquest total, de manera que tots els capítols imprimeixen el mateix nombre. El recompte mai no mou un salt de pàgina, així que n'hi ha prou amb una volta més; una configuració que no imprimeix{bookTotalPages}no paga res.- El Sandbox lliura el total a tots els capítols tan bon punt coneix les pàgines de cadascun. Fins llavors, un capítol imprimeix les pàgines fins al seu propi final. L'exportació del llibre sencer a la pestanya PDF compta les pàgines que maqueta: si un capítol va imprimir un altre recompte, perquè encara no se'n coneixien les pàgines, torna a maquetar el llibre amb el total al qual van arribar els capítols.
- Un amfitrió que compon els capítols pel seu compte passa el total com a
continuation.bookPageCount(també al primer capítol). Sense això,{bookTotalPages}compta les pàgines fins al final del document (continuation.pageIndexOffsetmés les seves), cosa que només és exacta per a l'últim capítol.
footer: {
elements: [{
kind: 'text', id: 'folio', content: '{pageNumber} / {bookTotalPages}',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'top' }, size: { width: 'auto', height: 'auto' } },
}],
}configUsesPlaceholder(config, 'bookTotalPages') diu a un amfitrió si val la pena calcular el recompte.
Paraules guia: primera i última marca
Un diccionari imprimeix a la capçalera el primer i l'últim lema de cada pàgina («Abacà – Àncora»); una obra de consulta, la primera i l'última secció. {firstMark.<clave>} i {lastMark.<clave>} els imprimeixen. La clau diu què marca una pàgina:
- De
h1ah6: un títol d'aquest nivell. La marca és el seu text, sense el número. - L'id d'un estil de paràgraf (
entry): un paràgraf d'un contenidor:::paragraphs{style="entry"}. La marca és el tram en negreta amb què obre el paràgraf, el lema, sense la puntuació final:**Abacá.** Planta de…marcaAbacá. Un paràgraf que no obre en negreta no posa marca.
{firstMark.<clave>} és la primera marca que comença a la pàgina i {lastMark.<clave>} l'última. Una pàgina en què no en comença cap (una entrada llarga que continua) imprimeix en tots dos la marca vigent, l'última anterior. Les pàgines anteriors a la primera marca no imprimeixen res; en un llibre maquetat capítol a capítol és la primera marca del capítol, perquè les marques no passen d'un capítol al següent. Un títol o un paràgraf partit entre pàgines marca només la pàgina en què comença. La clau s'escriu després del punt amb lletres, dígits, _ i -, començant per una lletra o _; una clau desconeguda no imprimeix res.
:::paragraphs{style="entry"}
**Abacà.** Planta de les Filipines de la fibra de la qual es fan caps.
**Abarloar.** Situar un vaixell al costat d'un altre o al costat d'un moll.
:::header: {
elements: [{
kind: 'text', id: 'guia', content: '{firstMark.entry} – {lastMark.entry}',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'bottom' }, size: { width: 'auto', height: 'auto' } },
}],
}Les paraules guia són capçaleres: es resolen a les ranures de capçalera i peu (també al header i al footer d'un estil de títol) i no imprimeixen res als dissenys de títol, de part i de l'índex; als de títol i de part, el tauler Revisió els assenyala com a marcadors desconeguts. Per mostrar el primer lema als versos i l'últim als rectos, fes servir dos elements amb parity: 'even' i parity: 'odd'. Un H1 l'estil d'encapçalament del qual fixa runningChapter: false no posa marca h1.
Capítol al cap de pàgina
{chapterTitle} i {chapterNumber} anomenen l'últim capítol començat a la pàgina o abans. En un llibre els capítols del qual van seguits, sense salt de pàgina entre ells, una pàgina que tanca un capítol i comença el següent a prop del peu porta llavors el títol del capítol nou sobre un text que encara pertany a l'anterior. {chapterTitleAtTop} i {chapterNumberAtTop} anomenen en canvi el capítol vigent al cap de la pàgina, com fan les novel·les de capítols seguits i moltes obres de consulta:
- una pàgina el primer bloc de la qual és l'H1 d'un capítol anomena aquest capítol;
- qualsevol altra pàgina anomena el capítol que continua, encara que més avall en comenci un de nou;
- una pàgina en blanc afegida per paritat va amb la pàgina que la segueix, i la separadora dels modes
always-*amb la que la precedeix (consulta Pertinença de les pàgines en blanc); si la pàgina que segueix una blanca de paritat comença amb el final d'un capítol i no amb el seu H1, la blanca també es queda amb aquest capítol; - se salta un H1 amb
runningChapter: false.
header: {
elements: [{
kind: 'text', id: 'chapter', content: '{chapterTitleAtTop}', parity: 'odd', pages: 'body',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'bottom-right' }, size: { width: 'auto', height: 'auto' } },
}],
}Com les paraules guia, tots dos són capçaleres: es resolen a les ranures de capçalera i peu i no imprimeixen res als dissenys de títol, de part i de l'índex. {attr.<clave>} llegeix sempre l'últim capítol començat.
Alineació implícita per vora
Quan un element de text té placement.anchor.to apuntant a un altre element mitjançant #id, la vora de l'ancoratge implica una alineació per defecte per a les línies ajustades:
right-ofialign-leftimpliquenalign: 'left'— les línies ajustades flueixen cap a la dreta des de l'ancoratge.left-ofialign-rightimpliquenalign: 'right'— les línies ajustades s'enganxen al costat més proper a l'element de referència.
L'editor d'encapçalaments del sandbox aplica aquestes alineacions implícites automàticament en canviar la vora o el destí de l'ancoratge. Mantenen el text ajustat visualment lligat a l'element amb què es relaciona (de manera que, p. ex., la "P" d'un "Postext" ajustat queda verticalment sota la "I" d'"Introducció").
Marques en línia i contorns
Un element de text compon el seu text en una sola font per defecte: **, ^ i els altres signes de marca de Markdown s'imprimeixen tal qual. Amb inlineMarks: true el text resolt es llegeix com a Markdown en línia, amb les mateixes marques que admet el text de cos (vegeu Format del document → Format en línia):
**negrita**compon amb gruix 700 (o amb elfontWeightde l'element si és més gruixut);*cursiva*inverteix la inclinació de l'element, de manera que l'èmfasi dins d'un element en cursiva surt en rodona;***ambas***fa totes dues coses. També valen les formes amb guió baix (__negrita__,_cursiva_).^superíndice^i~subíndice~es componen al 58 % de la mida, el superíndex elevat un terç d'aquesta i el subíndex baixat 0,15; un subíndex i un superíndex que es toquen (T~0~^2^) s'apilen, com al cos.- Una barra inversa compon el mateix signe de marca (
\*,\_,\^,\~). Un enllaç conserva el seu text; els accents greus de codi desapareixen.
Les marques es llegeixen després de substituir els marcadors, així que un valor les pot portar: la línia d'autors d'un atribut de l'encapçalament rep els seus números de filiació com a superíndexs. L'ajust de línia, la justificació, els modes d'el·lipsi, dropCap i paragraphIndent funcionen amb text marcat; tots els trams conserven el color de l'element.
{ "kind": "text", "id": "authors", "content": "{attr.authors}", "inlineMarks": true, "overflow": "wrap",
"fontSize": { "value": 11, "unit": "pt" },
"placement": { "anchor": { "to": "#title", "edge": "below" }, "offset": { "y": { "value": 6, "unit": "pt" } } } }# Innivació i cabal dels rius {authors="Ana Ruiz^1^, Luis Gil^2^ i Marta Sanz^1,3^"}stroke traça un contorn al voltant de les lletres: width és el gruix de la línia, centrada a les vores dels glifs (la meitat cau dins de les lletres i la meitat fora; l'amplada mesurada del text no canvia), color és per defecte el del text i hollow: true deixa les lletres sense farciment, de manera que només es veu el contorn: una xifra de titular perfilada, un títol que es desenganxa d'una fotografia. El contorn es pinta sobre el farciment, igual al llenç, en HTML (-webkit-text-stroke) i al PDF (mode de renderització de text 2, o 1 si és buit).
{ "kind": "text", "id": "year", "content": "1863", "fontFamily": "Bitter", "fontSize": { "value": 120, "unit": "pt" }, "fontWeight": 700,
"color": { "hex": "#1d3557", "model": "hex" },
"stroke": { "width": { "value": 1.5, "unit": "pt" }, "hollow": true },
"placement": { "anchor": { "to": "page", "edge": "bottom-right" }, "offset": { "x": { "value": -15, "unit": "mm" }, "y": { "value": -20, "unit": "mm" } } } }Al PDF, els trams en negreta i en cursiva incrusten les variants corresponents de la família de l'element, així que el proveïdor de fonts les ha de subministrar.
Valors per defecte del text i paranys de l'ancoratge
Un element de text que escrius tu parteix d'aquests valors, i algun sorprèn:
overflowval'ellipsis-end'. Un text massa ample per al seu espai es talla en una línia amb…. Fes serviroverflow: 'wrap'per a una adreça, una signatura o qualsevol títol que es pugui allargar. Un salt de línia en el contingut (un salt real, o\na la plantilla o al valor d'un atribut) comença una línia nova en tots els modes; els modes d'el·lipsi tallen cada línia per separat.alignval'center'iverticalAlignval'middle'. Un element d'amplada automàtica s'ajusta a la seva línia més llarga, de manera que les seves línies queden centrades entre si; fes serviralign: 'left'per a un bloc alineat a l'esquerra (un element d'amplada automàtica ancorat a un altre element alinea les seves línies tot sol del costat del seu ancoratge, vegeu més amunt).- La font és EB Garamond de 8 pt, en negre, amb
lineHeightde1.2, sigui quina sigui la del text de cos. A diferència dels altres interlineats de la configuració, ellineHeightd'un text de disseny sol ser un múltiple sense unitat (1.2); unDimensiontambé val (vegeu més amunt).
Un element sense placement.size.width (o amb 'auto') pren la mida del seu text, però només dins de l'espai que va del seu punt d'ancoratge a la vora del contenidor cap a la qual creix; per a un ancoratge top o bottom, el doble de la distància a la vora més propera. El seu offset compta. Un desplaçament que l'allunya d'aquesta vora no li treu res: una capçalera corrent ancorada a top-left amb una x negativa (que penja al marge esquerre) no perd espai, perquè creix cap a la dreta. Un desplaçament cap a aquesta vora li'n treu el mateix (el doble en un ancoratge top o bottom), i un que empeny el punt d'ancoratge més enllà de la vora el deixa sense espai: un ancoratge top-right amb una x menor que menys l'amplada del contenidor, o un ancoratge top desplaçat de costat més de la meitat d'aquesta amplada. Sense espai, un mode d'el·lipsi no imprimeix res i 'wrap' apila un caràcter per línia. Hi ha tres sortides:
- donar a l'element un
size.widthfix: les amplades fixes mai no es retallen; - ancorar-lo a
'page'o a'bleed', que converteix el marc de la pàgina (o de l'àrea de sang) en el seu espai; - ancorar-lo a la vora oposada del contenidor.
El contenidor de la capçalera va de la vora superior de tall al cos, i el del peu, del cos a la vora inferior de tall: a la capçalera, els ancoratges top-* es mesuren des de la vora de tall i els bottom-* des del cos, i a l'inrevés al peu (vegeu Marc del contenidor més amunt). Una capçalera o un peu mai no desplacen el text de cos i es pinten a sobre, així que un element empès a l'àrea del cos tapa el text. Una banda d'obertura, en canvi, es pinta sota el text de cos; l'espai que ocupa en el flux es descriu a Alçada reservada.
#Elements de text verticals
Un element de text amb writingMode: 'vertical-rl' es compon en vertical en un bloc el text del qual és horitzontal: les capçaleres i els folis de qualsevol llibre, que es queden al plec, i qualsevol disseny d'una pàgina horitzontal. Es compon com un text horitzontal en un marc propi girat un quart de volta en el sentit de les agulles del rellotge, i es retorna girat a la pàgina:
- La seva caixa queda on la posa la col·locació. La seva alçada és la llargada d'una línia: la fixa
size.height(o'auto', el que mesura el text;'fill', fins a la vora del contenidor),size.widthfixa quantes línies hi caben d'amplada isize.maxWidthlimita la llargada d'una línia. aligncol·loca les línies al llarg de la caixa ('left'a dalt),verticalAlignd'amplada ('top'a la dreta, on va la primera línia), i el farciment d'una caixa es queda al costat per al qual està escrit.- Els caràcters es mesuren i es pinten com en una pàgina vertical: els hanzi drets, a un quadratí cadascun; la puntuació, en la seva forma vertical; les paraules llatines, ajagudes; els nombres curts, en una casella (
cjk.uprightDigits). El canvas, el PDF i l'HTML ho posen al mateix rectangle. - Amb
inlineMarks: trueles marques d'orientació separen un tram com al cos:第:tcy[3.0]回posa 3.0 en una casella,:upright[GDP]posa les lletres dretes una sota l'altra,:sideways[…]ajau un tram; una línia mai no es talla dins d'una d'elles. Un element horitzontal les ignora. - Un element vertical no porta caplletra.
- En el flux d'una pàgina vertical (una obertura, una pàgina de part, el títol d'un requadre) el text ja va cap avall i
writingModeno canvia res.
Els llibres xinesos verticals posen capçaleres i folis en un de tres llocs (clreq §7.2; per al japonès, JLREQ §2.6):
| Convenció | On | Com es configura |
|---|---|---|
| Capçalera i peu horitzontals | Damunt i sota de la caixa de text, com als llibres horitzontals; és el més habitual. | La capçalera i el peu tal com són. |
| Marge exterior (estil 中缝; 邊峰 a Taiwan) | Al llarg del marge exterior: el títol del capítol o del llibre des d'uns quatre caràcters per sota del cap de la caixa, i el foli fins a uns cinc per damunt del peu, en nombres xinesos, a un 80 % del cos del text. | Dos elements verticals ancorats a 'outer', a sota. |
| Cantonada exterior del peu | El foli al peu de la pàgina, a la cantonada exterior (les normes de Taiwan per a llibres 中式). | Un element horitzontal al peu a 'bottom-left' amb parity: 'odd' i un altre a 'bottom-right' amb parity: 'even' en un llibre enquadernat per la dreta (a l'inrevés en un per l'esquerra). |
Les capçaleres del marge exterior, tal com les afegeix el Sandbox (Encapçalament › Capçaleres al marge exterior (vertical)), aquí per a un cos de 10 pt (el Sandbox les posa al 80 % del cos del text):
{
"page": { "pageNumbering": { "format": "trad-chinese-informal" } },
"header": { "elements": [
{ "kind": "text", "id": "head", "content": "{chapterTitle}", "writingMode": "vertical-rl",
"fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
"placement": { "anchor": { "to": "outer", "edge": "top" }, "offset": { "y": { "value": 4, "unit": "em" } } } },
{ "kind": "text", "id": "folio", "content": "{pageNumber}", "writingMode": "vertical-rl",
"fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
"placement": { "anchor": { "to": "outer", "edge": "bottom" }, "offset": { "y": { "value": -5, "unit": "em" } } } }
] }
}anchor.to: 'outer' (vegeu Posicionament d'elements) és el marge exterior de cada pàgina, així que els dos elements baixen per la vora esquerra d'una pàgina senar i per la dreta d'una de parella en un llibre enquadernat per la dreta (page.binding). {pageNumber} surt en el format de la numeració: trad-chinese-informal dona 一百零三 a la pàgina 103, i cjk-decimal, 一〇三. Les regles de cada bloc continuen valent: pages: 'body' treu una capçalera de les obertures de capítol. Un ornament curt entre capçalera i foli (una cua de peix ︻, un filet) és un element més ancorat al mateix marc.
Al VDT un bloc vertical porta vertical (VDTDesignTextBlock.vertical: la regió, les xifres dretes i l'eix central de cada família); les seves línies són al marc girat del mateix bloc: xOffset cap avall des de la vora superior de la caixa i baselineY cap a l'esquerra des de la seva vora dreta. Un tram d'una línia amb marques porta tcy o orientation, com un segment del cos.
#Elements de línia
Els elements de línia dibuixen una línia: horitzontal al llarg de l'amplada del bloc, o vertical al llarg de la seva alçada.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
kind | 'rule' | — | Discriminador. |
id | string | — | Id estable, únic dins del bloc, per a referències anchor.to: '#id'. |
direction | 'horizontal' | 'vertical' | 'horizontal' | Una línia horitzontal recorre placement.size.width ('fill' = fins a la vora del contenidor) i fa thickness d'alçada. Una vertical recorre placement.size.height ('fill' o sense definir = fins a la vora del contenidor) i fa thickness d'amplada — un separador entre el titolet i el foli. |
color | ColorValue | #000000 | Color del traç. |
thickness | Dimension | 0.5 pt | Gruix de la línia. Una línia que l'omet es dibuixa amb el valor per defecte (fins a postext 1.4 no pintava res). |
width | Dimension | 'full' | 'full' | 'full' abasta l'àrea de contingut; una Dimension restringeix la línia a una llargada fixa posicionada per align. |
align | 'left' | 'center' | 'right' | 'center' | Alineació quan width no és 'full'. |
marginFromBody | Dimension | 6 pt | Distància absoluta entre la vora de la línia que mira al cos i la vora del cos. Independent dels altres elements. |
marginFromEdge | Dimension | 0 pt | Desplaçament horitzontal respecte a la vora alineada. Només s'aplica quan width és una Dimension fixa i align és 'left' o 'right'. |
parity | 'all' | 'odd' | 'even' | 'all' | En quines pàgines apareix la línia. |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | Rols de pàgina en què apareix la línia (vegeu el camp pages dels elements de text). |
reserve | boolean | true | Només en dissenys d'encapçalament: si la línia compta per a l'alçada que l'encapçalament reserva en el flux del text (vegeu Alçada reservada). |
placement | ElementPlacement | derivat d'align + marginFromBody + marginFromEdge | Posicionament avançat (vegeu Posicionament d'elements). size.width / size.height fixen la longitud de la línia; width: 'fill' és l'antic 'full'. |
#Elements de caixa
Els elements de caixa pinten un rectangle arrodonit dins del bloc — útils com a fons darrere d'un text en obertures de capítol, columnes laterals o peus. Les caixes es posicionen exclusivament mitjançant el camp placement; no tenen variant plana heretada. L'emplenament, el traç i el radi de cantonada viuen a l'objecte niat style (ElementBoxStyle), com a l'exemple JSON de «Posicionament d'elements».
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
kind | 'box' | — | Discriminador. |
id | string | — | Id estable, únic dins del bloc. Els elements germans s'hi ancoren amb anchor.to: '#id'. El sandbox n'assigna un en crear l'element. |
style.backgroundColor | ColorValue | transparent | Color d'emplenament. Posa transparent per a una caixa només de vora. |
style.borderColor | ColorValue | transparent | Color del traç. |
style.borderWidth | Dimension | 0 pt | Gruix del traç. El traç es pinta cap a l'interior del rectangle de la caixa, de manera que les dimensions exteriors no varien: la vora exterior del traç va per la vora de la caixa, i una caixa arrodonida conserva el seu radi exterior. Un traç tan ample com la caixa l'omple. Canvas, HTML i PDF el dibuixen igual (fins a postext 1.4 el canvas i el PDF centraven el traç a la vora, amb la meitat fora de la caixa). |
style.borderRadius | Dimension | 0 pt | Radi de cantonada. Es limita a la meitat del costat més curt en temps de renderització. |
placement | ElementPlacement | — | Obligatori. Vegeu «Posicionament d'elements» més avall. |
parity | 'all' | 'odd' | 'even' | 'all' | En quines pàgines apareix la caixa. |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | Rols de pàgina en què apareix la caixa (vegeu el camp pages dels elements de text). |
reserve | boolean | true | Només en dissenys d'encapçalament: si la caixa compta per a l'alçada que l'encapçalament reserva en el flux del text (vegeu Alçada reservada). |
#Elements d'imatge
Un element image dibuixa un recurs de mapa de bits o SVG del document — el logotip de l'editorial a la portada, una marca en una capçalera. La seva mida la fixa placement.size: si un dels costats width / height queda en 'auto' (el valor per defecte), l'altre segueix la proporció de la imatge; amb tots dos fixats, la imatge s'ajusta dins de la caixa, centrada. Un recurs inexistent o que no sigui una imatge no dibuixa res.
{
kind: 'image', id: 'logo', resourceId: 'logo-editorial',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: { value: 64, unit: 'mm' }, y: { value: 233, unit: 'mm' } }, size: { width: { value: 83, unit: 'mm' }, height: 'auto' } },
}resourceId admet els mateixos marcadors que el content d'un element de text, de manera que un mateix disseny pot dibuixar una imatge diferent a cada encapçalament. Amb resourceId: '{attr.vineta}' en un estil d'encapçalament, # Capítulo I {style="apertura" vineta="tronco"} dibuixa el recurs tronco i # Capítulo II {style="apertura" vineta="peluca"} dibuixa peluca: els capítols comparteixen l'estil en lloc de clonar-lo per a cada imatge. Una capçalera o un peu llegeixen els atributs del capítol de la pàgina, com {attr.<clave>} en un titolet, i també hi serveixen els altres marcadors ('mapa-{chapterNumber}'). Un id que queda buit, com el d'un encapçalament sense l'atribut, no dibuixa res. Des de postext 1.8.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
id | string | — | Identificador estable; altres elements s'hi poden ancorar com a #id. |
resourceId | string | — | Id d'un Resource de mapa de bits o SVG del document. Pot portar marcadors, entre els quals {attr.<clave>}, que s'emplenen a cada encapçalament, part o pàgina. |
decorative | boolean | false | La imatge és només ornamental (un ornament, una banda): no aporta text alternatiu a la sortida encara que el seu recurs en tingui (vegeu més avall). |
placement | ElementPlacement | — | Ancoratge, desplaçament i mida (vegeu Posicionament d'elements). Un costat 'fill' arriba fins a la vora del contenidor. |
parity, pages | com a dalt | 'all' | En quines pàgines apareix la imatge. |
reserve | boolean | true | Només en dissenys d'encapçalament: si la imatge compta per a l'alçada que l'encapçalament reserva en el flux del text (vegeu Alçada reservada). |
El backend PDF incrusta el recurs com una figura (un SVG amb màster d'impressió l'utilitza); el visor HTML el resol mitjançant resourceImageUrl.
Una imatge que dibuixa un disseny és contingut quan el seu recurs la descriu: l'altText del recurs, o el seu peu com a text sense format quan no en té (una etiqueta :chip es llegeix pel seu text i una :ref pel seu text si en porta), passa al VDT (VDTDesignImageBlock.altText), és l'alt del seu <img> en HTML i una Figure amb /Alt en un PDF etiquetat, que es llegeix just després del text del seu disseny (la làmina d'un capítol, després del títol del capítol). Una imatge el recurs de la qual no té ni l'una cosa ni l'altra, i una marcada com a decorative, és ornament: alt="" amb role="presentation", i un artefacte al PDF. La imatge d'una capçalera o un peu es repeteix a totes les pàgines, així que es tracta com un element de paginació digui el que digui el seu recurs: sense altText al VDT, alt="" amb role="presentation" en HTML i un artefacte de la pàgina al PDF.
#Posicionament d'elements
ElementPlacement és el model unificat de posicionament que fan servir tots els tipus d'elements (text, línia, caixa) dins de qualsevol ranura de disseny — capçalera de pàgina, peu de pàgina o ranura de disseny avançat d'un encapçalament. Tres camps descriuen un placement:
interface ElementPlacement {
/** A què s'ancora aquest element i a quina vora d'aquest destí. */
anchor: {
to: 'container' | 'page' | 'bleed' | 'outer' | `#${string}`; // container = el bloc; page = caixa de tall; bleed = caixa de tall + sang; outer = el marge exterior (capçalera, peu); #id = un altre element
edge: AnchorEdge;
};
/** Distància des del punt d'ancoratge. */
offset?: { x?: Dimension; y?: Dimension };
/** Amplada / alçada opcional. L'amplada admet també 'fill' (estendre al llarg del bloc).
* `maxWidth` limita una amplada 'auto' (text): l'element continua ajustant-se al seu
* contingut, de manera que els elements que s'hi ancoren no se'n desenganxen, però un text llarg
* es talla o es plega allà — una capçalera pot reservar lloc per a l'etiqueta que
* en penja en comptes d'expulsar-la. */
size?: { width?: Dimension | 'fill' | 'auto'; height?: Dimension | 'fill' | 'auto'; maxWidth?: Dimension };
}AnchorEdge admet:
- Vores del contenidor (quan
anchor.toés'container','page'o'bleed'):top,top-left,top-right,bottom,bottom-left,bottom-right,left,right. - Vores relatives a un altre element (quan
anchor.to === '#someId'):right-of,left-of,below,above,align-top,align-bottom,align-left,align-right.
Cada vora relativa a un altre element posa una cantonada de l'element sobre una cantonada de l'element al qual s'ancora, i offset el desplaça des d'allà:
right-of: la seva cantonada superior esquerra sobre la superior dreta del de referència (al costat, amb les parts superiors a la mateixa alçada);left-of: la seva cantonada superior dreta sobre la superior esquerra del de referència;below: la seva cantonada superior esquerra sobre la inferior esquerra del de referència (a sota, amb les vores esquerres alineades);above: la seva cantonada inferior esquerra sobre la superior esquerra del de referència;align-topialign-left: la seva cantonada superior esquerra sobre la superior esquerra del de referència. Els dos noms donen la mateixa col·locació: s'alineen alhora les vores superiors i les esquerres;align-bottom: la seva cantonada inferior esquerra sobre la inferior esquerra del de referència;align-right: la seva cantonada superior dreta sobre la superior dreta del de referència.
Una vora relativa a un altre element usada amb 'container', 'page' o 'bleed', i una vora de contenidor usada amb '#id', es llegeixen com la cantonada superior esquerra.
anchor.to: 'page' ancora l'element a la caixa de tall (la pàgina física un cop tallada) i 'bleed' a la caixa de tall ampliada per cutLines.bleed a tots els costats (idèntica a la caixa de tall mentre les marques de tall estiguin desactivades). Tots dos marcs passen a ser també la referència de size: 'fill' i del retall automàtic d'amplada, de manera que una banda de color pot anar de vora a vora independentment dels marges de pàgina:
{ "kind": "box", "id": "band", "placement": { "anchor": { "to": "bleed", "edge": "top-left" }, "size": { "width": "fill", "height": { "value": 6, "unit": "cm" } } }, "style": { "backgroundColor": { "hex": "#1d3557", "model": "hex" } } }Amb les marques de tall activades, el que un element pinta fora de la caixa de sang es retalla (vegeu Marques de tall).
anchor.to: 'outer' (blocs de capçalera i peu) ancora l'element al marge exterior de la pàgina: de la vora de la caixa de text a la vora de tall al costat contrari al llom, i del cap de la caixa de text al seu peu. Queda a la dreta d'una pàgina senar i a l'esquerra d'una parella en un llibre enquadernat per l'esquerra, i al revés en un d'enquadernat per la dreta (page.binding), de manera que un sol element serveix per a les dues pàgines d'un plec: una capçalera pel marge exterior (vegeu Elements de text verticals). En qualsevol altre bloc es llegeix com 'container'. L'offset d'un element de text pot anar en em, quadratins del seu propi fontSize: quatre caràcters per sota del cap de la caixa de text és { "y": { "value": 4, "unit": "em" } }.
Dins de la ranura de disseny avançat d'un encapçalament, els elements ancorats a la pàgina o a la sang no augmenten l'alçada reservada per a l'encapçalament llevat que s'estenguin per sota de la seva vora superior (una banda a la part superior de la pàgina queda darrere de l'obertura; una banda que baixi més enllà de l'encapçalament empeny el cos cap avall). Fes servir advancedDesign.minHeight per reservar una alçada fixa d'obertura en qualsevol cas, i reserve: false en un element que no hagi d'empènyer el text de cap manera. Les regles completes són a Alçada reservada.
Cada element té un id estable (que assigna automàticament el sandbox; també el pots establir a mà). Els elements ancorats a altres elements formen un petit graf de dependències que el motor resol abans de mesurar, així un element es pot encadenar a un altre sense coordenades manuals.
La forma heretada align + marginFromBody + marginFromEdge s'interpreta a l'entrada i es reescriu a un placement en el moment de resoldre la configuració, de manera que les configuracions existents continuen funcionant sense canvis.
#Text de cos
La propietat bodyText controla la tipografia de tot el text de paràgraf.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily | string | 'EB Garamond' | Família tipogràfica per al cos de text. Qualsevol font de Google Fonts, del sistema, o declarada com a font personalitzada. Una sola família, no una llista de fonts CSS (vegeu més avall). |
fontSize | Dimension | 8 pt | Mida de font base per al cos de text. |
lineHeight | Dimension | 1.5 em | Espaiat vertical entre línies. Les unitats relatives (em, rem) s'escalen amb la mida de font. |
paragraphSpacing | boolean | false | Quan està activat, insereix una línia en blanc (igual al lineHeight) entre paràgrafs consecutius, com fan algunes editorials. |
color | ColorValue | #000000 | Color del text. |
boldColor | ColorValue | Color principal (#295AA3) | Color aplicat als fragments en negreta. Es resol contra l'entrada main-color de la paleta per defecte, de manera que canviar aquest color retinta tots els fragments en negreta del document. |
italicColor | ColorValue | Color principal (#295AA3) | Color aplicat als fragments en cursiva. Mateix enllaç a la paleta que boldColor. |
referenceColor | ColorValue | Color principal (#295AA3) | Color aplicat a les etiquetes de :ref en línia (referències a recursos). Mateix enllaç a la paleta que boldColor. Segueix la paleta des de postext 1.5; fins a la 1.4 es quedava en #295AA3 fos quin fos el color principal. |
referenceBold | boolean | true | Renderitzar les etiquetes de :ref en línia amb la font en negreta. |
referenceItalic | boolean | false | Renderitzar les etiquetes de :ref en línia en cursiva. |
emphasis | 'auto' | 'italic' | 'bold' | 'color' | 'overline' | 'auto' | Com es compon …: en cursiva, en la negreta i boldColor, en rodona amb italicColor, o en rodona amb una ratlla damunt de les paraules. 'auto' és 'bold' en un document escrit en alfabet àrab i 'italic' en qualsevol altre. Vegeu Text àrab. |
tashkil | 'keep' | 'strip' | 'strip-vowels' | 'keep' | Signes vocàlics de l'àrab: tal com estan escrits, tots suprimits, o suprimits llevat de la shadda. Vegeu Text àrab. |
textAlign | 'left' | 'justify' | 'start' | 'end' | 'justify' | Alineació del text. 'left' (o 'start') és el costat per on comença la línia: la dreta d'un paràgraf de dreta a esquerra (vegeu Direcció del text). El text justificat distribueix l'espaiat al llarg de cada línia per obtenir vores uniformes. Les últimes línies dels paràgrafs justificats es componen en bandera, a la seva amplada natural — llevat de quan Knuth-Plass ha acceptat una línia final sobreomplerta confiant en la compressió de la cola (glue): en aquest cas els espais entre paraules es comprimeixen perquè la línia encaixi exactament en la mesura (semàntica de glue-setting de TeX, aplicada de manera idèntica als backends canvas, HTML i PDF). |
fontWeight | number | 400 | Pes per al text normal (100–900). |
boldFontWeight | number | 700 | Pes per al text en negreta/strong (100–900). |
hyphenation | HyphenationConfig | activada, 'en-us' | Configuració de la partició de mots automàtica. Vegeu més avall. |
firstLineIndent | Dimension | 1.5em | Sagnat aplicat a la primera línia de cada paràgraf (o a totes les línies excepte la primera quan el sagnat francès està activat). |
hangingIndent | boolean | false | Quan està activat, el sagnat s'aplica a totes les línies excepte la primera (sagnat francès). |
indentAfterHeading | boolean | true | Quan val false, el primer paràgraf immediatament posterior a un encapçalament es compon sense sagnat de primera línia — convenció tipogràfica habitual en publicacions científiques i en molts estils editorials. El mateix val per a un paràgraf just després d'una línia :::space. Un requadre que surt del text entre tots dos —a la columna lateral (span: 'side'), flotat al cap o al peu d'una pàgina, o fix— i una figura flotant no compten: a la seva columna, el paràgraf segueix l'encapçalament i es compon sense sagnat. Un requadre compost en el text (placement: 'here') sí que compta, i el paràgraf que el segueix porta sagnat. No té efecte quan hangingIndent està activat. |
maxWordSpacing | number | 2 | Límit superior de l'espaiat entre paraules en text justificat, expressat com a multiplicador de l'amplada de l'espai normal. Knuth-Plass hi manté totes les línies que el paràgraf permet, i abans parteix una paraula o reparteix l'excés d'espai entre les línies veïnes; una línia que cap conjunt de talls no deixa dins del límit el supera, i la que passa de 3 vegades l'espai normal es compon en bandera. Les línies que superen aquesta proporció es consideren "fluixes": vegeu Línies que l'algorisme no pot omplir, i maxJustifyTracking perquè prenguin una mica de tracking en lloc seu. |
minWordSpacing | number | 0.6 | Límit inferior de l'espaiat entre paraules en text justificat, com a multiplicador de l'amplada de l'espai normal. |
maxJustifyTracking | number | 0 | Tracking màxim que pot prendre una línia justificada, en mil·lèsimes d'em en un sentit o en l'altre (la unitat d'InDesign: 10 = 0,01 em per caràcter), quan només amb els seus espais entre paraules s'estiraria més enllà de maxWordSpacing o es comprimiria per sota de minWordSpacing. La part de l'ajust que passa del límit va a les lletres, així que els espais d'una línia fluixa tornen a maxWordSpacing i una línia atapeïda hi cap amb minWordSpacing. Knuth-Plass el té en compte en triar els talls, i només a les línies que l'espaiat entre paraules duria més enllà dels límits: les altres, l'última línia d'un paràgraf (llevat que es desbordi), una línia d'una sola paraula i una línia amb un chip no el fan servir. La línia el desa com a letterSpacing, i canvas, HTML i PDF el pinten. Requereix optimalLineBreaking. Amb 0 queda desactivat. Vegeu Tracking com a últim recurs. |
kashida | 'auto' | 'none' | 'auto' en un document en alfabet àrab; si no, 'none' | Justificació amb caixida: una línia justificada de text en alfabet àrab pren el sobrant als espais entre paraules (fins a una quarta part de la seva amplada) i després en caixides, tatweels sencers (U+0640) inserits entre dues lletres enllaçades, mai com a espaiat entre lletres. Knuth-Plass compta l'allargament de cada paraula com a estirament. Mai en paraules llatines, xifres, títols, línies en bandera ni a l'última línia d'un paràgraf. Els tatweels es pinten però queden fora del text pla i del text copiat. Vegeu Caixida en el text àrab. |
kashidaPatterns | 'auto' | 'naskh' | 'simple' | 'nastaliq' | 'auto' | Quins enllaços prenen caixida i en quin ordre: les regles clàssiques del naskh, les prioritats de Microsoft o les regles del naskh adaptades al nastaʿlīq (segons raqim-kashida). 'auto' mira la font del text: cap en un tipus ruqʿa o dīwānī (Aref Ruqaa), regles de nastaʿlīq en un tipus nastaʿlīq, naskh en els altres. |
kashidaPerWord | number | 1 | Màxim d'allargaments en una paraula. |
kashidaMaxLength | number | 0.6 | Allargament màxim en un enllaç, en em; en pren tants tatweels sencers com hi càpiguen. |
optimalLineBreaking | boolean | true | Fer servir el tall òptim de línies Knuth-Plass en lloc del voraç de primer ajust. Produeix un espaiat entre paraules més uniforme en tot el paràgraf. Un paràgraf en xinès, japonès o coreà (vegeu Tipografia de l'Àsia oriental), o amb una paraula més ampla que la columna, es compon igualment línia a línia; un paràgraf llatí que cita unes poques paraules CJK el conserva. El text en bandera també el fa servir amb optimalRagged. Vegeu Partició de mots i justificació. |
optimalRagged | boolean | true | Compon també amb Knuth-Plass el text corregut en bandera: el text de cos, les cites i els elements de llista alineats a l'esquerra, a la dreta o centrats, i els estils de paràgraf, els cossos de requadre i els cossos de les parts i dels estils de secció en bandera. Els espais entre paraules no canvien d'amplada. L'algorisme pondera quant es queda curta cada línia respecte de la mesura (una línia 3 em més curta costa el mateix que una línia justificada amb maxWordSpacing), així que iguala el marge irregular en lloc d'omplir cada línia abans de passar a la següent, i les regles contra les línies curtes (avoidRunts, tightenRunts) i hyphenateAcrossColumns actuen en el text en bandera com en el justificat. Amb hyphenation.ragged, la zona continua decidint quines síl·labes poden acabar una línia (vegeu Text en bandera). Els títols en bandera, els peus, les notes, les cel·les de taula i l'índex de continguts es continuen component línia a línia. Requereix optimalLineBreaking. false compon el text en bandera línia a línia, com fins a postext 1.4; així es llegeixen les configuracions desades abans que posen en bandera algun text corregut (vegeu Paquets escrits per postext 1.4 o anterior). |
breakAfterDashes | boolean | true | Permet que una línia acabi després d'un guió o d'un guió curt posat entre dues paraules sense espais: Madrid–Barcelona, I.—Que trata (un títol de capítol a l'antiga) o, en un text en anglès, say—that’s, també quan la paraula que segueix el guió va en un altre estil (calidad–precio). Knuth-Plass el pren com un espai entre paraules, i la línia acaba al guió sense afegir-hi res. Mai després del guió que obre un incís o un diàleg (—dijo, dijo "—Hola, sagte »—Ich: amb un espai, o un espai i unes cometes, davant del guió; després d'unes cometes que tanquen una paraula, com a «no»—y, l'alemany „nein“—und o el francès « non »—et, la línia sí que pot acabar), davant d'un signe de puntuació (él—,), davant d'unes cometes o un parèntesi (pensaba—» y, dijo—«no», dijo—(no): després d'un guió, unes cometes solen tancar la cita que el guió interromp), dins d'una sèrie de guions ni dins d'un interval de números amb guió curt (1914–1918). false manté els talls de la 1.4: Knuth-Plass mai no talla després d'un guió, i el divisor línia a línia del text amb format o del text en bandera amb partició de mots només entre dues lletres; així es llegeixen les configuracions desades abans el text de les quals té algun d'aquests guions (vegeu Paquets escrits per postext 1.4 o anterior). S'aplica al text corregut, els títols, les llistes, les cites i els requadres. Els peus, les notes, les cel·les de taula i l'índex de continguts conserven els talls de la 1.4, i un paràgraf en bandera sense format compost línia a línia segueix en tots dos casos les regles pròpies de pretext. |
breakAfterHyphens | boolean | true | Permet que una línia acabi després del guionet d'un compost, un guionet entre dues lletres (físico- · química, vencer- · se), en qualsevol paràgraf que talla Knuth-Plass. La línia acaba al guionet i no s'hi afegeix res; el tall costa el mateix que una síl·laba. Mai després d'un guionet al costat d'una xifra o un signe (COVID-19, -5 °C). Un paràgraf justificat sense format en línia només hi talla amb dues lletres a cada costat del guionet, perquè cap línia no acabi en la e- d'e-mail. false manté els talls de la 1.4: un paràgraf justificat sense format en línia mai no hi talla, mentre que el mateix paràgraf amb una paraula en cursiva en qualsevol lloc, un paràgraf en bandera i un de compost línia a línia sí que ho fan; així es llegeixen les configuracions desades abans el text de les quals té algun compost (vegeu Paquets escrits per postext 1.4 o anterior). S'aplica al text corregut, els títols, les llistes, les cites i els requadres. Vegeu Paraules compostes. |
repeatHyphen | boolean | false | Comença també amb un guionet la línia que segueix un tall al guionet d'un compost: léxico- · -semántico, com demanen les normes de la Real Academia Española des del 2010, i vencer- · -se, com demana l'ortografia portuguesa. El guionet repetit es mesura i es pinta amb la seva línia, que l'anota com a repeatedHyphen; el seu plainStart i el seu sourceStart apunten darrere seu, de manera que els enllaços, els titolets i el Sandbox llegeixen la paraula tal com està escrita. El PDF el pinta sota un /ActualText que l'omet, així que el text que es copia o s'extreu del PDF llegeix la paraula una sola vegada. Una adreça web mai no el porta. S'aplica al text corregut, els títols, les llistes, les cites i els requadres; un paràgraf sense format que conté un compost es talla llavors amb el divisor del text amb format. |
blockquote | BlockquoteConfig | vegeu a sota | Com es componen les cites de Markdown (> …): color, cursiva i sagnats. Vegeu Cites. |
#Cites
Una cita (les línies que comencen per >) pren la família, el cos, l'interlineat, els pesos, l'alineació i la partició de mots del text de cos. bodyText.blockquote fixa la resta; si no es fixa, una cita es veu com fins a postext 1.4: grisa, en cursiva, amb el sagnat de primera línia del cos i sense sagnat lateral.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
color | ColorValue | #666666 | Color del text. Un color enllaçat a una entrada de la paleta (paletteId) segueix aquella entrada, com arreu; els colors de negreta, cursiva i referències del cos no s'apliquen dins d'una cita. |
italic | boolean | true | Compon el text en cursiva. Un tram … dins torna a rodona; amb false va en cursiva, com en un paràgraf. |
indent | Dimension | 0 | Sagnat de totes les línies des de la vora esquerra de la columna o del requadre. La mesura s'estreny en aquesta quantitat, de manera que les línies justificades acaben a la vora dreta. em és el cos del text. |
firstLineIndent | Dimension | la del cos | Sagnat de la primera línia de cada paràgraf citat, comptat des d'indent (amb el hangingIndent del cos, el de totes les línies llevat de la primera). Si no es fixa: bodyText.firstLineIndent. |
bodyText: {
firstLineIndent: { value: 1.5, unit: 'em' },
// Versos en rodona, en el color del cos, entrats 2 em i sense sagnat de primera línia.
blockquote: { color: { hex: '#241f26', model: 'hex' }, italic: false, indent: { value: 2, unit: 'em' }, firstLineIndent: { value: 0, unit: 'em' } },
}Al Sandbox són el grup Cites de la secció Text base.
#Una sola família per fontFamily
fontFamily —aquí i en qualsevol altre camp de família tipogràfica (headings.fontFamily, tableStyle.bodyFontFamily, separatorFontFamily, el fontFamily d'un estil de xip o d'un element de disseny…)— anomena una família. El canvas, la sortida HTML i el PDF han de compondre amb la mateixa font, i el PDF incrusta una font per família, sense cadena de reserva, de manera que una llista de fonts CSS no té a què recórrer. Una llista es compon en la seva primera família i es notifica com a avís de configuració:
bodyText: { fontFamily: "'EB Garamond', Georgia, serif" } // es compon en EB GaramondCarrega aquesta família abans de compondre (consulta Fonts personalitzades i el proveïdor de fonts a Generació de PDF); si falta, el navegador mesura amb la seva font per defecte, digui el que digui la resta de la llista. Una coma entre cometes forma part del nom ('"Foo, Bar"' és una sola família).
#Partició de mots
Quan l'alineació del text està configurada com a 'justify', la partició de mots evita l'espaiat excessiu entre paraules en trencar les paraules llargues pels límits sil·làbics. El motor utilitza patrons TeX/Liang per trobar punts de ruptura naturals en els límits sil·làbics. Vegeu Partició de mots i justificació per a una explicació detallada. El text en bandera només es divideix si es demana: vegeu Text en bandera més avall.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | true | Si es permet la partició de mots. |
locale | LocaleTag | el locale de primer nivell o, si no n'hi ha, 'en-us' | Regles de llengua per als límits sil·làbics: una de les llengües admeses de sota o qualsevol etiqueta BCP 47 ('es-ES', 'pt-BR'). |
ragged | boolean | false | Parteix mots també en el text en bandera (alineat a l'esquerra, a la dreta o centrat), dins de la zone. Vegeu Text en bandera. |
zone | Dimension | 3em | Zona de partició del text en bandera: una paraula que no hi cap només es divideix quan passar-la sencera a la línia següent deixaria un buit més gran que aquesta mesura. En em és relativa al cos del mateix text. No s'aplica al text justificat. |
compounds | boolean | true | Permet que el diccionari divideixi les paraules d'un compost, una paraula amb un guionet entre dues lletres (teó-rico-práctico). false deixa la paraula sencera excepte pel seu propi guionet, on la línia sí que pot acabar (teórico- · práctico), com fa TeX. Un guionet tou escrit a la paraula continua permetent el tall, i un compost més ample que tota la línia es continua dividint. S'aplica al text corregut, els títols, les llistes, les cites i els requadres; els peus, les notes, les cel·les de taula i l'índex de continguts continuen dividint els compostos. Vegeu Paraules compostes. |
Llengües admeses: 'en-us' (anglès), 'es' (castellà), 'fr' (francès), 'de' (alemany), 'it' (italià), 'pt' (portuguès), 'ca' (català), 'nl' (neerlandès).
Per triar els patrons s'ignoren les subetiquetes de regió, escriptura i variant, les majúscules i els separadors _: 'es-ES', 'es-MX' i 'es_419' es divideixen amb 'es', 'pt-BR' amb 'pt' i qualsevol etiqueta anglesa ('en', 'en-GB') amb 'en-us', els únics patrons anglesos inclosos, de manera que el text britànic rep els talls nord-americans. Una llengua sense patrons inclosos ('sv', 'pl', 'fi'…) es divideix amb els d''en-us', cosa que dona talls equivocats en lloc de cap; el motor ho avisa un cop per etiqueta amb un console.warn, i el Sandbox ho mostra al seu tauler de revisió. Per a un document així, fixa enabled: false; això també fa callar l'avís. El xinès, el japonès i el coreà (zh, ja, ko, amb qualsevol regió o escriptura) no necessiten patrons ni imprimeixen cap avís: un document en una d'aquestes llengües es compon sense partició de mots. Per dividir les paraules llatines que cita, fixa enabled: true i anomena'n la llengua a locale ('en-us' per a l'anglès). Amb enabled: true sense locale, o amb un de xinès, japonès o coreà, la partició continua desactivada i la consola ho avisa un cop. matchHyphenationLocale(tag) retorna la llengua inclosa a la qual correspon una etiqueta (undefined si no n'hi ha cap), i HYPHENATION_LOCALES enumera les incloses. La configuració resolta (doc.config.bodyText.hyphenation) anomena a locale els patrons que s'usen de debò i conserva a tag l'etiqueta que vas donar quan és diferent. El backend PDF declara la llengua del document a partir de locale, a l'arrel de la configuració, i només fa servir aquesta etiqueta quan locale falta (consulta Llengua del document).
import { matchHyphenationLocale } from 'postext';
matchHyphenationLocale('es-MX'); // 'es'
matchHyphenationLocale('en-GB'); // 'en-us'
matchHyphenationLocale('sv'); // undefined: es divideix amb 'en-us', amb un avís a la consolaLes paraules de menys de 5 caràcters no es parteixen mai. El motor requereix com a mínim 2 caràcters abans i 3 caràcters després d'un punt de ruptura.
Text en bandera
Per defecte només es divideix el text justificat: amb textAlign: 'left', i en els estils de paràgraf alineats a l'esquerra, centrats o a la dreta, totes les paraules es mantenen senceres per irregular que quedi el marge, excepte en un guionet tou (U+00AD) escrit al text. Fixa hyphenation.ragged: true per partir mots també en el text en bandera.
Una línia en bandera no s'estira mai, de manera que dividir tota paraula que no hi cap ompliria el marge de guionets. La zona de partició ho limita, com fa el compositor d'una sola línia dels programes de maquetació. Quan una paraula no cap al final d'una línia, el motor mira el buit que deixaria passar-la sencera a la línia següent. Si aquest buit és més gran que zone, divideix la paraula per l'última síl·laba que hi cap; si no, la paraula baixa sencera. Una zona en em és relativa al cos del mateix text. La zona per defecte, 3em, només divideix les paraules que deixarien una línia clarament curta. Una zona més ampla dona menys guionets i un marge més irregular; amb 0 es divideix tota paraula que no hi cap. Línia a línia, mai no acaben més de dues línies seguides en síl·laba. Els guionets propis de la paraula (enseñanza-aprendizaje), les juntures de les URL, els guionets tous escrits al text i les paraules més amples que la línia sencera es tallen com sempre: la zona i el límit de dues línies només governen les síl·labes del diccionari.
L'ajust val per a tot el document. S'aplica al text de cos i a les cites quan van en bandera, i a tot estil de paràgraf i cos de requadre en bandera que tingui activada la seva pròpia hyphenation (que pren per defecte el hyphenation.enabled del cos), de manera que hyphenation: false manté senceres les paraules d'un estil. Els títols, els peus, les notes, les cel·les de taula i l'índex de continguts no es divideixen; en aquests, com arreu, només es talla una paraula més ampla que la mesura sencera. El text de disseny — capçaleres corregudes, portadelles, pàgines de part — segueix la marca hyphenate de cada element de text, que porten activada les portadelles de pàgina completa i les pàgines de part integrades, i també fa servir el diccionari del document. El text justificat ignora ragged i zone.
const config: PostextConfig = {
locale: 'es',
bodyText: {
textAlign: 'left',
// Una mica més de partició de mots que amb la zona per defecte de 3 em.
hyphenation: { ragged: true, zone: { value: 2, unit: 'em' } },
},
};Amb optimalRagged (el valor per defecte), el text corregut en bandera es compon amb Knuth-Plass, i la zona conserva el seu sentit: una paraula només es divideix quan no cap en el que queda de línia i passar-la sencera a baix deixaria un buit més gran que la zona. Dues síl·labes seguides no es rebutgen, però costen el mateix que dos guionets seguits en el text justificat, de manera que una tercera és rara. Amb optimalRagged: false, o amb optimalLineBreaking: false, cada línia s'omple abans de passar a la següent, com fins a postext 1.4.
La partició de mots en bandera compon els paràgrafs amb el divisor de línies que fan servir sempre els paràgrafs amb format en línia (negreta, cursiva, enllaços, fórmules), de manera que activar-la pot moure algun tall que no és un guionet. En una línia en bandera, a més dels espais i de les síl·labes del diccionari, aquest divisor talla després d'un guionet o un guió entre dues paraules (largas—separadas; amb breakAfterDashes, després de qualsevol guió posat entre paraules sense espais, també I.—Que i calidad–*precio*), a les juntures de les URL i entre ideogrames, i manté unida una paraula escrita en diversos trams (**Nota**:, (*véase*). Sense partició de mots en bandera, un paràgraf en bandera sense format el talla Knuth-Plass quan optimalRagged està activat (el valor per defecte): als espais, després d'un guionet entre dues lletres (enseñanza- · aprendizaje), com l'altre divisor, i, amb breakAfterDashes, després dels guions entre paraules. Línia a línia, passa pel divisor de pretext, que difereix en tres coses: pot tallar també abans del guió que tanca un incís o després del que l'obre (él · — y), després d'una barra quan divideix síl·labes (km/ · h), i talla una paraula més ampla que la línia per qualsevol caràcter i sense guionet, on l'altre divisor la parteix abans per una síl·laba.
Al Sandbox l'interruptor és Partició de mots en bandera, sota l'alineació de paràgraf de la secció Text base, amb la zona a sota. Amb el cos justificat, el mateix interruptor apareix al costat dels ajustos de justificació, per als estils de paràgraf i requadres en bandera.
#Llengua del document
El locale de primer nivell és la llengua del document en conjunt. Admet els mateixos valors que hyphenation.locale i és el valor al qual recorre aquest camp quan no està definit, de manera que a un llibre en castellà li n'hi ha prou amb locale: 'es' per partir mots en castellà. També tria la llengua de les cadenes integrades de continuació de taules i d'avisos partits ((cont.) / Continued davant de Continúa, vegeu Taules més altes que la pàgina i Marques d'un requadre partit) i la dels números d'encapçalament escrits amb lletres (Chapter One davant de Capítulo uno, vegeu Números amb lletres), i és la llengua que s'etiqueta en un PDF accessible. Si no es defineix, el motor assumeix 'en-us'; el Sandbox recorre a l'idioma de la interfície i mostra el camp a Disseny › Escriptura.
També tria els tipus de recurs integrats. Quan resourceTypes no està definit, buildDocument numera i retola amb defaultResourceTypes(locale), de manera que amb locale: 'de' n'hi ha prou per obtenir Abbildung 1.1 i Tabelle 1.1. Si locale no està definit, el substitueix la llengua de la partició de mots, tant per als tipus de recurs com per a les cadenes de les taules. Una llista resourceTypes explícita sempre mana. Les versions anteriors feien servir els tipus en anglès fos quin fos el locale, tret que passessis tu mateix defaultResourceTypes(locale); un document que fixa locale i vol conservar les etiquetes angleses passa resourceTypes: defaultResourceTypes('en'). Els llibres del Sandbox i els paquets porten la seva pròpia llista, així que no els afecta.
Aquí val qualsevol etiqueta BCP 47, igual que a hyphenation.locale: 'de-AT' rep les cadenes alemanyes. Les cadenes integrades existeixen en les vuit llengües que admet la partició de mots i en xinès, en caràcters simplificats i tradicionals; qualsevol altra llengua rep les angleses.
El xinès admet zh, zh-Hans, zh-Hant, zh-CN, zh-SG, zh-TW, zh-HK, zh-MO i les formes llargues (zh-Hant-TW), en majúscules o minúscules i amb - o _. Les cadenes segueixen l'escriptura, llegida amb Intl.Locale(tag).maximize(): zh, zh-CN i zh-SG són xinès simplificat; zh-TW, zh-HK i zh-MO, tradicional. Els valors tipogràfics que depenen de la llengua segueixen en canvi la regió, com recomana clreq §1.2: CN, SG i MY compten com a Xina continental, TW com a Taiwan, HK i MO com a Hong Kong, i una etiqueta sense regió es regeix per la seva escriptura (zh-Hant és Taiwan; zh i zh-Hans, la Xina continental). localeScript(tag), cjkRegionOf(tag), stringsKeyOf(tag) i sameContentLocale(a, b) retornen aquestes lectures, i DOCUMENT_LANGUAGES enumera les llengües amb cadenes integrades, cadascuna amb el seu nom en la seva pròpia llengua, tal com les mostra el selector de llengua del document del Sandbox.
| Llengua | Figura: nom, plural, etiqueta curta | Taula: nom, plural, etiqueta curta | Continuació de taula: continuedSuffix, continuesMarker |
|---|---|---|---|
Anglès (en) | Figure, Figures, Fig. | Table, Tables, Tab. | (cont.), Continued |
Castellà (es) | Figura, Figuras, Fig. | Tabla, Tablas, Tabla | (cont.), Continúa |
Francès (fr) | Figure, Figures, Fig. | Tableau, Tableaux, Tabl. | (suite), À suivre |
Alemany (de) | Abbildung, Abbildungen, Abb. | Tabelle, Tabellen, Tab. | (Forts.), Wird fortgesetzt |
Italià (it) | Figura, Figure, Fig. | Tabella, Tabelle, Tab. | (segue), Continua |
Portuguès (pt) | Figura, Figuras, Fig. | Tabela, Tabelas, Tab. | (cont.), Continua |
Català (ca) | Figura, Figures, Fig. | Taula, Taules, Taula | (cont.), Continua |
Neerlandès (nl) | Figuur, Figuren, Fig. | Tabel, Tabellen, Tab. | (vervolg), Wordt vervolgd |
Xinès simplificat (zh-Hans, zh, zh-CN) | 图, 图, 图 | 表, 表, 表 | (续), 接下页 |
Xinès tradicional (zh-Hant, zh-TW, zh-HK) | 圖, 圖, 圖 | 表, 表, 表 | (續), 接下頁 |
Àrab (ar, ar-EG, ar-MA…) | شكل, أشكال, شكل | جدول, جداول, جدول | (تابع), يتبع |
El prefix del peu és el nom del tipus (Figura 1.1.). Els tipus xinesos numeren dins del capítol amb un guionet, {h1}-{n} (图 1-1); amb els ajustos de peu labelNumberGap: '' i labelSeparator: ' ' el peu es llegeix 图1-1 标题 (vegeu Estil de peus de recurs). L'índex alfabètic també segueix la llengua: 见 i 另见 davant d'una remissió, 符号 i 数字 sobre els símbols i els números en xinès simplificat; 見, 另見, 符號 i 數字 en tradicional. L'àrab numera els seus tipus de la mateixa manera, {h1}-{n} (شكل 2-3), escriu les referències creuades com الفصل 3, القسم 2-1 i ص 12, titula la bibliografia المراجع, i el seu índex imprimeix انظر / انظر أيضًا, رموز i أرقام, amb la coma i el punt i coma àrabs (، ؛).
Un document en xinès, japonès o coreà declara la seva llengua a la sortida HTML (lang a l'arrel .pt-doc, amb zh-Hant-TW complet) i al canvas que pinta (ctx.lang, a Chrome 136 i posteriors), de manera que el navegador dibuixa les formes de glif de la seva regió: Unicode unifica els caràcters han, i un mateix punt de codi es veu diferent en una font taiwanesa i en una de japonesa. Els altres documents no porten lang, com abans. El PDF declara /Lang a partir de locale per a tot document, etiquetat o no, amb la seva escriptura i la seva regió. Un document en una llengua que s'escriu de dreta a esquerra (àrab, persa, urdú, hebreu…) també declara la seva llengua: les formes de la llengua de la font (locl) i la font de reserva la segueixen.
const config: PostextConfig = {
locale: 'es',
bodyText: { textAlign: 'justify', hyphenation: { enabled: true } }, // parteix mots en castellà
};Xifres del document
La clau numerals de primer nivell fixa les xifres de tots els números que escriu el motor: els números de pàgina i les etiquetes de pàgina de l'índex general, de l'índex alfabètic i de les referències a pàgina; els números de les llistes ordenades i de les notes; els comptadors de títols i capítols, {chapterNumber}; el {h1} i el {n} del número d'una figura i d'una referència a aquesta; {totalPages}, {bookTotalPages} i {numberDecimal}. 'latn' escriu 0–9, 'arab' les xifres aràbigues orientals ٠–٩ i 'arabext' les perses ۰–۹. Només canvia el format decimal —decimal, l'arabic de les llistes o un ajust que es deixa per defecte—, de manera que un format que l'autor anomena s'imprimeix tal com s'anomena: lower-roman continua sent i, ii, iii, i arabic-indic en un document llatí escriu ١, ٢, ٣. El text del document no es reescriu mai.
El valor per defecte, 'auto', pren les xifres de locale (defaultNumeralsFor(tag)): 'arab' per a l'àrab sense regió o amb qualsevol regió fora del Magrib (ar, ar-EG, ar-SA, ar-AE…), 'latn' per a ar-MA, ar-DZ, ar-TN, ar-LY, ar-MR i ar-EH, 'arabext' per al persa (fa), el paixtu (ps) i l'urdú de l'Índia (ur-IN), i 'latn' per a tota la resta, inclòs l'urdú del Pakistan. CLDR dona latn per a ar sense regió i per a ar-AE; els llibres àrabs del Màixriq i del Golf imprimeixen ٠–٩, i Postext segueix els llibres. Una etiqueta que anomena les seves xifres les conserva: ar-MA-u-nu-arab. Una pàgina numerada amb les xifres del document desa arabic-indic o persian com a pageNumberFormat, de manera que les etiquetes de pàgina del PDF mostren les mateixes xifres. Un valor desconegut segueix la llengua i s'avisa com a unknownNumerals.
Els números que escriu l'autor es llegeixen en qualsevol dels tres sistemes: un element de llista ٣. comença a 3, i {startAt=٥}, :::numbering{startAt=٥}, :::space{lines=٢} i :::part{number="٣"} (per a {numberDecimal}) llegeixen el valor.
const config: PostextConfig = { locale: 'ar' }; // ١، ٢، ٣
const magreb: PostextConfig = { locale: 'ar-MA' }; // 1, 2, 3
const forzado: PostextConfig = { locale: 'ar', numerals: 'latn' }; // 1, 2, 3Direcció del text
direction, al primer nivell, fixa la direcció base del document: 'ltr', 'rtl' o 'auto' (el valor predeterminat), que és 'rtl' quan l'escriptura del locale s'escriu de dreta a esquerra (àrab, persa, urdú, hebreu, siríac, thaana, n'ko, adlam…, segons directionOf(tag)) i 'ltr' en els altres casos. Un document de dreta a esquerra es compon en un marc emmirallat: les línies comencen a la dreta, la primera columna és la de la dreta, els sagnats, els marcadors de llista, els flotants, les notes a peu de pàgina i les caixes queden a la dreta, i page.binding: 'auto' l'enquaderna per la dreta. La configuració resolta només porta direction: 'rtl' en un document així, de manera que un document d'esquerra a dreta es resol igual que abans. Un valor desconegut es llegeix com a 'auto' i s'avisa com a unknownConfigValue. L'algorisme bidireccional d'Unicode (UAX #9) ordena els trams de cada línia en qualsevol de les dues direccions: una cita àrab en un llibre anglès es llegeix de dreta a esquerra al seu lloc.
Dins del document, un títol o un contenidor ::: admet {dir=ltr} o {dir=rtl}, i :ltr[…] i :rtl[…] aïllen un tram de text (vegeu Direcció del text al marcatge); un recurs de taula admet table.direction. Un bloc compost en contra de la direcció del document conserva el seu propi costat d'inici: el sagnat, els marcadors de llista i el final alineat de l'última línia passen al costat per on comença el seu text.
Un ajust que anomena un costat es refereix a un costat del text o del flux, mai del plec, de manera que un disseny fet per a un llibre anglès continua valent quan el llibre passa a l'àrab. 'start' i 'end' s'accepten com a noms explícits:
| Ajust | 'left' / 'right' | 'start' / 'end' |
|---|---|---|
textAlign del cos, els títols, els estils de paràgraf, les parts, les notes a peu de pàgina i el cos dels avisos; align del peu i de la seva nota | Els costats del text: 'left' és el costat per on comença la línia, la dreta d'un paràgraf àrab, i allà va l'última línia d'un paràgraf justificat. | Sinònims de 'left' i 'right'. La configuració resolta porta 'left' / 'right'; la desada conserva el que s'hi va escriure. |
align d'una cel·la de taula | Els costats del text de la cel·la, llegits en la direcció de la taula (table.direction). | Sinònims, llegits en la direcció de la taula. |
placement.align (flotants, figures estretes) | Els costats del flux: en un llibre de dreta a esquerra, 'left' és la dreta del plec. | Sinònims. |
stripe.side, icon.cornerSide i labelTab.position dels avisos ('top-start', 'top-end') | Els costats del flux, els mateixos per a totes les caixes de la pàgina. | La direcció de la caixa mateixa (un :::callout{dir=ltr} en un llibre àrab comença per l'esquerra del plec). |
| Ranures de capçalera i peu; elements de disseny ancorats al plec | Els costats del plec. | Només els elements de text: l'inici i el final de la seva pròpia direction. |
placement.rotate conserva el seu sentit físic en una pàgina emmirallada: una figura girada en el sentit de les agulles del rellotge queda girada així al plec. Qui llegeix la maquetació troba el marc emmirallat a cada pàgina (VDTPage.flow amb direction: 'rtl', pageIsMirrored(page)) i l'ordre visual dels segments de cada línia a VDTLine.order; flowToPage i pageToFlow passen del flux al plec i a l'inrevés. Vegeu Composició àrab.
const arab: PostextConfig = { locale: 'ar' }; // de dreta a esquerra, enquadernat per la dreta
const angles: PostextConfig = { locale: 'en', direction: 'rtl' }; // forçat; poques vegades és el que vols#Llengües i escriptures
Postext compon escriptures alfabètiques que s'escriuen d'esquerra a dreta, i el xinès en horitzontal i en vertical. Composició xinesa explica com es compon el xinès i quins ajustos el governen; les claus són a Tipografia de l'Àsia oriental, Escriptura vertical i Enquadernació. El que rep cada escriptura:
- El xinès el compon el compositor CJK quan un paràgraf té més caràcters CJK que espais entre paraules: les seves línies es tallen entre caràcters amb les regles de principi i final de línia de
cjk.lineBreak(cap línia no comença per 。、」 o ー, cap no acaba en 「 o (), mantenen senceres —— i ……, un número amb els seus signes i una paraula llatina, i una línia justificada s'eixampla entre els seus caràcters fins a la mesura. L'amplada de la puntuació, la puntuació penjada, l'espai entre xinès i llatí, la retícula de caràcters, els punts d'èmfasi, les marques de nom propi i de títol, el ruby i les notes warichu segueixen la regió delocale, al llarg de la línia horitzontal o de la vertical (layout.writingMode: 'vertical-rl'). Un paràgraf llatí que cita unes poques paraules CJK conserva la divisió òptima de línies i es pot tallar al costat d'aquestes; un signe d'obertura o tancament CJK, el punt volat o un signe d'amplada completa citats en text llatí (〈h〉, %) no canvien res. - El japonès i el coreà passen pel mateix compositor i es componen sense partició de mots, però amb els valors per defecte de la Xina continental: les seves regles pròpies (JLREQ, KLREQ) no estan implementades. El coreà es talla entre síl·labes a més de pels seus espais.
- L'àrab i les altres escriptures de dreta a esquerra (persa, urdú, hebreu…) es componen de dreta a esquerra; Composició àrab explica com. La
directiondel document surt de l'escriptura dellocale, l'algorisme bidireccional d'Unicode ordena les paraules llatines i les xifres dins de cada línia, el llibre s'enquaderna per la dreta amb la primera columna a la dreta, i cada número que escriu el motor pren les xifres de la regió. Una paraula amb una lletra de l'alfabet àrab no es parteix, no s'espaia ni es talla mai, i una línia àrab justificada s'estira als espais i amb caixides. Els signes vocàlics, l'èmfasi, les notes a peu de pàgina i les cadenes de l'àrab són a Text àrab. El persa, l'urdú i l'hebreu reben la direcció, les xifres i les regles de paraula sencera, però no cadenes integrades pròpies.
Hi ha patrons de partició de mots per a vuit llengües (en-us, es, fr, de, it, pt, ca, nl); els tipus de recurs integrats i les cadenes de continuació existeixen en aquestes vuit, en xinès i en àrab. El xinès, el japonès, el coreà i les llengües que s'escriuen de dreta a esquerra es componen sense partició de mots. Qualsevol altra llengua es divideix amb els patrons de l'anglès dels EUA, amb un avís a la consola, i rep les cadenes angleses. Per a un document així, fixa hyphenation.enabled: false i passa resourceTypes i les cadenes de continuació de tableStyle en la seva llengua.
#Text àrab
Aquests ajustos serveixen el text en alfabet àrab; cap no canvia un document escrit en una altra escriptura. Composició àrab els explica juntament amb la resta d'un llibre àrab: direcció, enquadernació, xifres, vers, índex general i índex analític.
- Signes vocàlics i interlineat. Els signes vocàlics d'un text vocalitzat (fatḥa, kasra, shadda, tanwīn, l'alif volat, els signes alcorànics) s'apilen sobre i sota les lletres, a l'interlineat, que no creix per ells. Cada línia amb signes anota fins on arriba la seva tinta (
VDTLine.markInk), i el retall de columna dels renderitzadors inclou els signes de la primera i l'última línia de cada columna. Quan un signe sobre una paraula toca les lletres o els signes que pengen sota la paraula de sobre, la composició avisa del paràgraf (arabicMarksExceedLeading, al tauler Revisió del Sandbox). Només es comparen paraules que estan una sobre l'altra. El text vocalitzat en part demana uns 1,7–1,85 em delineHeight, i el vers vocalitzat del tot 1,9–2,1 em. - Èmfasi. La lletra àrab no té cursiva, així que en un document el
localedel qual s'escriu en alfabet àrab*…*es compon per defecte en negreta (bodyText.emphasis: 'auto').'color'el compon en rodona ambitalicColor, i'overline'traça una ratlla per sobre de les paraules, el khaṭṭ fawqī dels llibres àrabs. L'ajust abasta tot text compost amb les fonts del cos: paràgrafs, llistes, cites, estils de paràgraf, cos dels requadres, notes i encapçalaments. Els peus, les cel·les de taula, l'índex general i l'índex analític conserven els seus propis ajustos de cursiva. Es triï el que es triï, el motor no inclina mai lletres àrabs: les paraules àrabs d'un tram en cursiva van en rodona i les seves paraules llatines conserven la cursiva. En un document així, les cites van en rodona per defecte. - Tashkīl.
bodyText.tashkil: 'strip'treu els signes vocàlics i alcorànics del text que es compon, per a una edició sense vocals feta a partir d'una font vocalitzada: fatḥa, ḍamma, kasra i els seus tanwīn, sukūn, shadda, l'alif volat (هٰذا passa a هذا) i els signes U+0656–U+065F i U+06D6–U+06ED.'strip-vowels'conserva la shadda, tal com la imprimeixen la majoria dels llibres moderns. La hamza i la madda es queden (أ إ آ són lletres, també teclejades amb signes combinants). La font conserva els seus signes; les línies, els encapçalaments i l'índex general es componen sense aquests signes, i cada caràcter compost continua apuntant al seu lloc a la font. - Notes a peu de pàgina.
footnotes.markerTemplate: '({n})'escriu les crides «(١)» amb les xifres del document,numbering: 'page'les reinicia a cada pàgina inoteNumberPosition: 'inline'posa sobre la línia el número de la nota. El filet separador i els números de nota queden al principi de la columna, a la dreta en un llibre de dreta a esquerra. - Paraules senceres. Una paraula amb una lletra de l'alfabet àrab no es parteix, no es talla ni s'espaia mai, en un llibre àrab o citada en un altre. Un estil que posa
letterSpacinga un text àrab s'avisa (joiningScriptLetterSpacing), i una paraula més ampla que la seva línia la desborda i s'avisa (unbreakableWordOverflow). Vegeu Composició àrab. - Caixida i vers. Una línia àrab justificada s'estira amb caixides a més dels espais (
bodyText.kashida, vegeu Caixida en el text àrab), i un poema clàssic es compon un bayt per línia en dos hemistiquis d'una mateixa amplada amb:::verse(vegeu:::verse). - Índex analític. Un índex en àrab ordena alfabèticament i no té en compte l'article ال (
index.ignoreArticle), els signes vocàlics ni els suports de la hamza; vegeu Índex analític.
#Òrfenes, vídues, runts i regles de cohesió
Consulta Partició de mots i justificació per entendre la mecànica de demèrits que hi ha al darrere. Aquesta secció és la referència de les claus de bodyText que governen aquestes penalitzacions.
Més enllà de la partició de mots i els límits d'espaiat, la configuració del cos exposa les regles toves que eviten els salts de paràgraf estructuralment incòmodes. Totes s'injecten com a demèrits a l'algorisme de salt de línia Knuth-Plass: decanten la maquetació cap a salts nets sense imposar mai una regla dura. Posa a 0 qualsevol *Penalty per desactivar aquella penalització.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
avoidOrphans | boolean | true | Desaconsellar que un paràgraf acabi amb menys de orphanMinLines línies al principi de la columna següent. |
orphanMinLines | number | 2 | Línies mínimes requerides al principi de la columna següent quan un paràgraf es parteix. Només actiu quan avoidOrphans és true. |
orphanPenalty | number | 1000 | Demèrit afegit quan s'incompleix la restricció d'òrfenes. Valors més alts decanten més fortament l'algorisme; 0 desactiva la penalització. |
avoidOrphansInLists | boolean | true | Quan és true, els elements de llista també reben protecció contra òrfenes (no només els paràgrafs). Només té efecte si avoidOrphans és true. |
avoidWidows | boolean | true | Desaconsellar que un paràgraf comenci amb menys de widowMinLines línies al final de la columna actual. |
widowMinLines | number | 2 | Línies mínimes requerides al final de la columna actual quan un paràgraf es parteix. Només actiu quan avoidWidows és true. |
widowPenalty | number | 1000 | Demèrit afegit quan s'incompleix la restricció de vídues. 0 desactiva la penalització. |
avoidWidowsInLists | boolean | true | Quan és true, els elements de llista també reben protecció contra vídues. Només té efecte si avoidWidows és true. |
avoidRunts | boolean | true | Desaconsellar que els paràgrafs acabin amb una línia curta: una última línia molt breu, p. ex. una única paraula curta aïllada. També en el text en bandera, amb optimalRagged. Un paràgraf xinès, japonès o coreà no acaba en una línia d'un sol caràcter, sol o amb els signes que el tanquen (孤字): la línia anterior li cedeix el seu últim caràcter si encara es pot justificar dins del límit d'espaiat. |
runtMinCharacters | number | 20 | Llindar per a l'última línia d'un paràgraf, comptat en espais entre paraules, no en lletres: la línia és curta quan és més estreta que runtMinCharacters × anchoDeEspacioNormal píxels. En la majoria de tipus de lletra de text un espai fa entre un quart i un terç d'em, més o menys mitja lletra minúscula, així que el valor per defecte, 20, arriba a les últimes línies de menys de 4 a 7 em: unes 8 a 12 lletres. Per arribar a les de menys d'unes N lletres, posa-hi un valor proper a 2 × N. |
runtPenalty | number | 1000 | Penalització equivalent a badness (s'injecta a la fórmula quadràtica de Knuth–Plass, a la mateixa escala que el badness de línia, que se satura a 10000). 0 desactiva la penalització. |
gradedRuntPenalty | boolean | false | Gradua la penalització per línia curta segons com de curta es queda l'última línia: una última línia d'amplada w per sota del llindar t costa runtPenalty × (1 − w / t) en lloc de la penalització sencera. Així, un final de dues paraules costa menys que un d'una, i l'algorisme baixa una paraula quan una línia de dalt la pot cedir («…sallies of» / «our minds.» en lloc de «…sallies of our» / «minds.»). Desactivat per defecte: totes les línies curtes costen el mateix, i l'algorisme conserva les línies més atapeïdes de dalt. |
avoidRuntsInLists | boolean | true | Quan és true, els elements de llista també reben la penalització per línia curta. Només té efecte si avoidRunts és true. |
tightenRunts | boolean | true | Quan la penalització no ha pogut evitar una línia curta, compon el paràgraf amb una línia menys: els espais s'estrenyen (mai per sota de minWordSpacing) i, si amb això no n'hi ha prou, hi entra a més una mica de tracking negatiu. La composició més curta es descarta, i la línia curta es queda, si estira una línia justificada més enllà de maxWordSpacing o, si el paràgraf ja té una línia justificada més oberta, més que aquella línia, o si compon en bandera (més de 3 vegades l'espai normal) més línies que el paràgraf. Els espais del text en bandera no canvien d'amplada, així que allà només hi intervé el tracking. Requereix optimalLineBreaking i avoidRunts (i optimalRagged en el text en bandera). |
maxRuntTracking | number | 10 | Tracking màxim que pot prendre aquest ajust, en mil·lèsimes d'em (la unitat d'InDesign: 10 = 0,01 em per caràcter), aplicat com a estrenyiment. Amb 0 l'ajust es limita a l'espaiat entre paraules. |
slackWeight | number | 10 | Pes aplicat al cost quadràtic de l'"espai de columna no utilitzat". Valors més alts fan que la maquetació prefereixi omplir les columnes al màxim; 0 desactiva aquesta pressió del tot. |
keepColonWithList | boolean | true | Quan un paràgraf acaba amb dos punts que introdueixen directament una llista, manté la línia dels dos punts unida a la llista: si col·locar el paràgraf no deixa lloc perquè el primer element de la llista comenci a la mateixa columna/pàgina, l'última línia (o el paràgraf sencer, si només té una línia) es mou a la columna següent juntament amb la llista. Quant lloc és suficient ho diu colonListRoom. Quan aquesta regla hagi d'empènyer el paràgraf complet i just abans hi hagi una seqüència de títols a la columna, aquests títols també s'arrosseguen endavant perquè headings.keepWithNext es continuï complint. |
colonListRoom | 'item' | 'line' | 'item' | El lloc que demana keepColonWithList sota la línia dels dos punts. 'item': el que les regles d'òrfenes i vídues de les llistes deixarien del primer element al peu de la columna, una línia si s'hi pot partir, tot sencer si el mantenen sencer (un element de dues línies, per exemple). 'line': una línia, com fins a postext 1.4; un primer element que aquestes regles mantenen sencer passa llavors tot sol a la columna següent i deixa la línia dels dos punts al peu. Una configuració desada abans de configVersion 6, en un llibre que introdueix una llista amb dos punts, es llegeix amb 'line' (vegeu Paquets escrits per postext 1.4 o anterior). Qualsevol altre valor es llegeix com a 'item'. |
hyphenateAcrossColumns | boolean | true | Permet que una columna o una pàgina acabi en una paraula partida (l'Hyphenate Across Column d'InDesign). Amb false, el paràgraf que travessa el salt de columna es torna a tallar perquè la seva última línia a la columna acabi en una paraula sencera; els espais entre paraules de les línies de dalt absorbeixen la diferència, dins de maxWordSpacing i minWordSpacing. És una preferència: on cap tall dins d'aquests límits no ho evita, el guionet es queda. Ho intenta en tots els salts de columna d'un paràgraf: en el primer i en els següents que cauen on acaba una columna plena, amb un nou tall; i en un salt posterior que cau en un altre lloc (una vídua evitada, una banda tallada a nivell), amb un altre nou tall des d'aquella columna, que deixa les línies ja col·locades a les columnes anteriors amb els seus talls i només torna a tallar la resta. No afecta el cos dels requadres. Amb optimalRagged, un paràgraf en bandera es torna a tallar igual: els seus espais entre paraules no canvien d'amplada, així que només es mouen els finals de les seves línies. Cada nou tall mesura el paràgraf una vegada més, així que un llibre amb molts guionets al peu de columna triga una mica més a compondre's. Necessita optimalLineBreaking, i optimalRagged en el text en bandera. |
paragraphContainerSpacing | 'collapse' | 'add' | 'collapse' | L'espai sota un contenidor :::paragraphs que es tanca amb un paràgraf, entre aquest paràgraf i el bloc següent (vegeu El contenidor :::paragraphs). 'collapse': el més gran entre el spaceBetween i el marginBottom de l'estil i la separació entre paràgrafs del text que envolta el contenidor (una línia amb paragraphSpacing; en un requadre, la del requadre), fos amb l'espai que el bloc següent deixa sobre seu, com entre dos paràgrafs del text corregut: un encapçalament sota una bibliografia en queda separat pel seu propi marginTop, no per aquest marge més la separació de les entrades. 'add': com fins a postext 1.4, sota l'última línia només s'hi posa l'espai de l'estil abans de l'ajust a la retícula, l'espai que el bloc següent deixa sobre seu se suma a sota i la separació entre paràgrafs no compta, així que el paràgraf que segueix el contenidor podia quedar-hi més enganxat que a qualsevol altre. Una configuració desada abans de configVersion 8 que declara algun estil de paràgraf, en un llibre que té un d'aquests contenidors, es llegeix amb 'add' (vegeu Paquets escrits per postext 1.4 o anterior). Un marginBottom negatiu puja el bloc següent en tots dos casos. |
Sobre les línies curtes. Una línia curta (runt, en anglès) és l'última línia d'un paràgraf quan és massa curta per semblar una línia de text pròpiament dita, típicament una o dues paraules curtes encallades al final del paràgraf. Com que la comprovació es basa en l'amplada en píxels de la línia relativa a l'amplada de l'espai normal, runtMinCharacters s'adapta automàticament a la mida de font actual. Una paraula curta visualment més ampla que runtMinCharacters × anchoDeEspacio és vàlida; una paraula més estreta que això (o veritablement sola) dispara la penalització. El llindar compta espais entre paraules, que fan més o menys la meitat que una lletra: el valor per defecte, 20, equival a una última línia d'unes 8 a 12 lletres. Tota línia curta costa la penalització sencera, així que entre dos finals per sota del llindar l'algorisme conserva les línies més atapeïdes de dalt; gradedRuntPenalty valora cadascun segons el que li falta i guanya el final més llarg. Per a qui tingui curiositat matemàtica: amb el runtPenalty per defecte de 1000, evitar una línia curta domina sobre qualsevol alternativa que exigís un estirament de l'espai entre paraules de fins a aproximadament r≈2,15.
Toves, no dures. Cap d'aquestes regles no pot impedir un salt: el motor sempre produeix una maquetació. Són demèrits: l'algorisme combina en una única optimització global la "badness" (desigualtat), el cost de la partició de mots, la suavitat de classe d'encaix i aquestes penalitzacions estructurals, i tria el conjunt de salts amb el cost total més baix. Si necessites una garantia més dura, puja la penalització; si un document concret es llegeix millor amb la penalització relaxada, abaixa-la.
#Encapçalaments
La propietat headings controla la tipografia de tots els nivells d'encapçalament (H1–H6). Pots establir valors generals per defecte que s'apliquen a tots els nivells, i després sobreescriure propietats específiques per nivell.
#Valors generals per defecte
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily | string | 'Open Sans' | Família tipogràfica per a tots els encapçalaments. |
lineHeight | Dimension | 1.2 em | Alçada de línia per als encapçalaments. Més ajustada que el cos de text. |
color | ColorValue | Color principal (#295AA3) | Color del text d'encapçalament. Enllaçat a l'entrada main-color de la paleta per defecte, de manera que canviar aquest color retenyeix tots els encapçalaments. |
textAlign | 'left' | 'justify' | 'center' | 'right' | 'start' | 'end' | 'left' | Alineació de tots els nivells d'encapçalament (no hi ha un valor per nivell): en bandera, justificada, centrada o alineada a la dreta. Un encapçalament justificat compon la seva última línia a l'esquerra, com un paràgraf, així que un encapçalament d'una sola línia es veu com 'left'. Canvas, HTML i PDF col·loquen les línies de la mateixa manera, prefix de numeració inclòs. L'obertura per defecte d'un encapçalament span: 'page' sense disseny avançat també la segueix (justificada queda a l'esquerra); un disseny avançat alinea els seus propis elements de text amb el seu align. |
fontWeight | number | 700 | Pes de font per als encapçalaments (100–900). |
marginTop | Dimension | 1.5 em | Espai sobre els encapçalaments. |
marginBottom | Dimension | 0.5 em | Espai sota els encapçalaments. |
keepWithNext | boolean | true | Quan és true, un encapçalament mai no es col·loca com a últim element d'una columna o pàgina. Si el bloc següent no tingués almenys bodyText.widowMinLines línies de buit després de l'encapçalament (o una línia quan bodyText.avoidWidows és false), l'encapçalament s'empeny endavant per quedar unit al seu text. Interactua amb bodyText.keepColonWithList: si aquesta regla ha d'empènyer del tot el paràgraf de dos punts, els encapçalaments que el precedeixen a la columna viatgen amb ell en lloc de quedar encallats. |
keepWithNextSplit | 'rules' | 'fill' | 'rules' | Com es divideix el paràgraf que segueix un encapçalament quan l'encapçalament queda al peu d'una columna i empènyer el paràgraf sencer el deixaria enrere. 'rules': totes les línies que hi càpiguen, sempre que en quedin almenys bodyText.widowMinLines sota l'encapçalament i en passin almenys bodyText.orphanMinLines a la columna següent; si cap divisió no compleix les dues coses, l'encapçalament passa a la columna següent amb el seu paràgraf, i l'equilibrat de columnes omple el buit que deixa. 'fill': totes les línies que hi càpiguen, encara que en passin molt poques, així que un paràgraf de quatre línies amb lloc per a tres es divideix 3 + 1. Fins a postext 1.4 tots els encapçalaments es dividien així, i les configuracions desades abans de configVersion 8 ho conserven (vegeu Paquets escrits per postext 1.4 o anterior). Amb avoidWidows o avoidOrphans desactivat, aquella part de la regla no s'aplica. |
snapToGrid | boolean | true | Si el flux torna a la retícula de base sota un títol. Amb true el marginBottom del títol s'arrodoneix a línies senceres de la retícula; amb false es conserva el marge exacte i el text sota el títol pot quedar fora de la retícula fins al punt d'ajust següent (el final d'una llista, la cua d'un :::paragraphs, una equació), com molts llibres, que deixen línia i mitja sota el títol. Un nivell (levels[].snapToGrid) o un estil d'encapçalament poden fixar el seu propi valor; aquest és el que hereten. |
inlineMarks | boolean | true | Si un encapçalament llegeix les seves marques en línia com ho fa un paràgraf: cursiva, negrita, ^superíndice^, ~subíndice~, :smallcaps[…] i enllaços. Un tram en cursiva inverteix la inclinació de l'encapçalament, així que en un encapçalament en cursiva surt en rodona; un tram en negreta pren bodyText.boldFontWeight, o el pes del mateix encapçalament si és més gran. L'índex també recull els trams en negreta i en cursiva; els titolets i els marcadors del PDF imprimeixen només el text. Amb false es treuen els marcadors i les paraules s'imprimeixen amb l'estil de l'encapçalament, com fins a postext 1.4. L'obertura per defecte d'un encapçalament span: 'page' sense disseny també compon els trams en negreta, en cursiva, en superíndex i en subíndex; el disseny d'un encapçalament (una banda d'obertura amb disseny o un advancedDesign de columna) imprimeix com a text sense marques en tots dos casos. Les configuracions desades abans amb encapçalaments que porten marques es llegeixen amb false (vegeu Paquets escrits per postext 1.4 o anterior). |
balancing | ColumnBalancingConfig | activat | Equilibrat vertical de columnes: espai addicional sobre els encapçalaments perquè les columnes acabin alineades amb el peu de pàgina. Vegeu més avall. |
Encapçalaments centrats per a poemes o els actes d'una obra de teatre, sense ranura de disseny:
headings: {
textAlign: 'center',
levels: [{ level: 2, textTransform: 'uppercase' }],
}Un objecte headings conserva tots els valors per defecte de nivell que no repeteixis, inclòs el salt de pàgina d'H1 (vegeu Configuració per nivell).
#Equilibratge de columnes
Les editorials esperen que cada columna comenci a la part superior de la pàgina i acabi alineada amb la part inferior. Les regles de ruptura (protecció d'òrfenes i vídues, títols units al seu text, figures indivisibles) deixen columnes curtes de manera natural — una o més línies de retícula de base buides al final. Amb l'equilibratge activat, el motor fa el que faria un maquetista, aplicant les seves palanques en ordre de prioritat editorial:
- Una caixa que tanca la columna — un callout que acaba una columna curta baixa exactament l'espai que queda sota el seu peu, de manera que la seva vora inferior cau a l'última casella de retícula de la pàgina, a nivell amb l'última línia de la columna contigua. Ocupa aquest espai encara que sigui menor que una línia, sempre que res no passi a una altra columna. Per defecte actua abans que qualsevol altra palanca i es queda tot el buit, de manera que un requadre que comenta el paràgraf de damunt pot acabar diverses línies per sota d'aquest; amb
closingBox: 'last'els encapçalaments, els finals de llista i les altres palanques d'espai prenen abans les línies senceres, i el requadre només el que deixen, i ambclosingBox: 'off'no es mou mai. - Encapçalaments — s'afegeixen línies completes de retícula al marge superior dels encapçalaments de la columna curta. Quan calen diverses línies i la columna conté diversos encapçalaments, les línies es reparteixen entre ells, deixant sempre la major part a l'encapçalament més important (un
h2en rep més que unh3). Els encapçalaments situats al principi d'una columna no reben mai espai addicional, de manera que les columnes continuen començant a la part superior — llevat d'un encapçalament just sota una figura o taula que encapçala la seva columna en una pàgina que flueix cap a la següent: l'espai va damunt d'aquest encapçalament, sota la figura. - Finals de llista — quan els encapçalaments no poden absorbir tot el buit, s'afegeix una línia de retícula on acaba una llista o enumeració (l'aire després d'una llista es llegeix amb naturalitat), amb un màxim per llista.
- Paràgrafs correguts — com a últim recurs, es recompon un paràgraf de la columna amb una línia més (el
\looseness=+1de TeX), triant el paràgraf més llarg perquè l'espaiat addicional es dilueixi de manera invisible. La solució correguda només s'accepta quan totes les seves línies queden per sota debodyText.maxWordSpacing— el color tipogràfic mai no supera el límit que ja tinguis configurat. RequereixbodyText.optimalLineBreaking.
L'última columna d'una pàgina només s'equilibra quan la pàgina flueix de manera natural cap a la següent — la pàgina final d'un capítol acaba curta de manera legítima. Aquesta pàgina, i una banda de tancament tallada a nivell per trailing, mantenen a més els caps de les seves columnes a la mateixa altura: no s'afegeix cap línia sota una figura o taula que encapçali una de les seves columnes (la palanca després de flotants), i un encapçalament o una caixa que obre una columna sota aquesta figura es queda al seu cap, digui el que digui stretchAfterFloats, de manera que cap columna no comença més avall que la veïna només per igualar els peus.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | true | Si s'equilibren els finals de columna. |
maxLinesPerHeading | number | 4 | Nombre màxim de línies de retícula addicionals que es poden afegir damunt d'un mateix encapçalament. |
stretchAfterLists | boolean | true | Permet línies de retícula addicionals on acaba una llista, quan els encapçalaments no poden absorbir tot el buit. |
maxLinesAfterList | number | 1 | Nombre màxim de línies de retícula addicionals després del final d'una mateixa llista. |
stretchAfterFloats | boolean | true | Permet línies de retícula addicionals sota una figura o taula que encapçala la columna curta (un flotant superior), després de la palanca dels finals de llista, de manera que el text de sota baixa en lloc d'acabar la columna curta. Mai en una pàgina que no flueix cap a la següent (la pàgina final d'un capítol) ni en una banda de tancament tallada a nivell per trailing: allà els caps de les columnes queden a la mateixa altura i l'última columna pot acabar una línia més curta. El primer bloc sota la figura pot ser la continuació d'un paràgraf començat a la pàgina anterior: baixa igualment, la línia que les regles de tall van deixar lliure al peu de la columna (una línia que la regla de vídues va deixar buida, o un espai entre paràgrafs sense lloc per a text al darrere). Amb false, el text queda just a sota de la figura. |
maxLinesAfterFloat | number | 1 | Nombre màxim de línies de retícula addicionals sota un mateix flotant superior. |
looseParagraphs | boolean | true | Últim recurs: recompon paràgrafs d'una columna curta amb una línia més (una línia extra cadascun), sense superar bodyText.maxWordSpacing. |
maxLooseParagraphs | number | 2 | Quants paràgrafs d'una mateixa columna curta poden guanyar una línia, els més llargs primer. |
trackParagraphs | boolean | true | Quan amb l'espaiat entre paraules no n'hi ha prou per guanyar la línia, el paràgraf corregut pot prendre a més el menor tracking positiu (interlletratge) que ho aconsegueixi. |
maxTracking | number | 10 | Límit d'aquest tracking, en mil·lèsimes d'em per caràcter (10 = 0,01 em). |
trailing | boolean | true | Anivella la banda de tancament de cada capítol i del document: quan el flux acaba abans d'omplir la pàgina (en una obertura de capítol, un :::part, una caixa placement: 'fixed' que tanca el capítol o el final del document) amb les columnes desiguals, es tallen a la mateixa altura — un topall de banda de ceil(Σ usado / N / rejilla) línies, resolt després que les palanques anteriors hagin assentat les pàgines prèvies —, de manera que una bibliografia curta acaba a la mateixa altura a totes les columnes en lloc d'omplir la primera i deixar l'última a mitges. El tall respecta les regles d'una banda sense tallar: quan un bloc que no s'hi pot partir (el final d'un paràgraf que els mínims d'òrfenes i vídues mantenen sencer, una caixa que no es parteix) el sobrepassaria — i perdria les seves últimes línies, perquè els renderitzadors retallen cada columna a la seva caixa — o un encapçalament tancaria una columna mentre el seu text obre la següent (amb headings.keepWithNext, el valor per defecte), el tall baixa una línia, fins a tres vegades, i si no n'hi ha prou es descarta. Un bloc que no pot començar a les línies que el tall deixa sota una figura que encapçala una columna (un paràgraf hi necessita el seu mínim d'òrfenes) passa a la columna següent de la banda, com ho faria des d'una columna sense tallar, i la figura queda sola a la seva columna. Les columnes els peus de les quals difereixen en una línia de la retícula o menys es deixen com estan: una banda de tancament l'última columna de la qual acaba una línia més curta és el final habitual, i tallar-la només mouria una línia. El tall va a la banda en què es va mesurar, igual que el que demana una caixa d'amplada de pàgina a mitja pàgina: fins a postext 1.4, quan el primer text de la pàgina de tancament s'havia ofert abans a una banda sense lloc per a ell (una pàgina que ocupa sencera un flotant, la banda estreta que deixa al peu de la pàgina anterior una caixa d'amplada de pàgina), el tall es gastava en aquesta banda i la pàgina de tancament deixava totes les seves línies a la primera columna. Les columnes d'amplades diferents (una disposició de columna i mitja amb text a les dues) es tallen per superfície, ponderant l'altura de cada columna per la seva amplada, perquè una línia de la columna estreta porta menys text. Només actua quan enabled és true. |
beforeSpan | boolean | true | Anivella la banda que deixa enrere un bloc d'amplada de pàgina: quan un avís span: 'page' no cap sota les columnes actuals ni tan sols després d'un tall anivellat, i ha de passar a la pàgina següent o partir-se (calloutStyles[].keepTogether: false), les columnes que interromp es tallen a la mateixa altura — el mateix topall de tancament que rep una banda final — en lloc que la primera ompli la pàgina i l'última acabi curta. La pàgina queda com a salt explícit perquè les palanques anteriors no tornin a estirar la seva última columna fins al peu; la caixa, o la part que hi cap, queda llavors sota les columnes anivellades. Només actua quan enabled és true. |
closingBox | 'first' | 'last' | 'off' | 'first' | Quan pren un requadre que tanca una columna curta l'espai sota el seu peu (la palanca 1, registrada com a trailingCallout). 'first': abans que qualsevol altra palanca, com fins a postext 1.4 — el requadre es queda tot el buit, encara que siguin diverses línies, i els encapçalaments de damunt no reben res. 'last': després de les palanques d'encapçalaments, finals de llista, fórmules i flotants, que prenen abans les línies senceres; el requadre baixa amb el text de damunt i després pren només el que han deixat, normalment una fracció de línia, de manera que el seu peu continua caient a l'última casella de la retícula i queda a prop del text que comenta. 'off': mai; el requadre conserva l'espai sota el seu peu, tot i que les altres palanques encara el poden baixar línies senceres quan afegeixen espai per damunt, i la fracció de línia que sobra s'hi queda. En una pàgina de tancament, on el requadre és l'única palanca que alinea el seu peu amb la columna contigua, 'off' el deixa on l'ha posat el flux. Qualsevol altre valor es llegeix com a 'first'. |
Quina palanca ha actuat
La maquetació deixa constància de cada palanca que aplica, al bloc sobre el qual l'aplica: block.balancing al VDT que retorna buildDocument. Una prova d'impremta, un test o un informe poden dir per què una columna acaba alineada — i quines columnes no va poder tancar cap palanca. Els blocs que l'equilibratge deixa com estaven no porten balancing, i un document compost amb enabled: false no el porta en cap.
interface VDTBalancing {
levers: BalanceLever[]; // normalment una: un paràgraf després d'una llista pot prendre la línia de final de llista i a més guanyar una línia
spaceAbove: number; // px que les palanques d'espaiat han afegit damunt del bloc (0 si només ha actuat looseParagraph)
extraLines?: number; // looseParagraph: línies que ha guanyat el paràgraf
tracking?: number; // looseParagraph: el tracking amb què les ha guanyades, en mil·lèsimes d'em (0 = només amb l'espaiat entre paraules)
}
type BalanceLever = 'trailingCallout' | 'heading' | 'listEnd' | 'afterDisplay' | 'afterFloat' | 'looseParagraph';| Palanca | Es registra a | Què ha fet |
|---|---|---|
trailingCallout | el bloc del marc de l'avís | Una caixa que tanca la columna ha baixat exactament l'espai que quedava sota el seu peu (spaceAbove; pot ser una fracció de línia). |
heading | l'encapçalament | Línies senceres de retícula damunt de l'encapçalament, fins a maxLinesPerHeading. |
listEnd | el primer bloc després de la llista | Una línia de retícula on acaba una llista, fins a maxLinesAfterList. |
afterDisplay | el bloc després de la fórmula o la caixa | Una línia de retícula sota una fórmula en bloc o una caixa d'avís. |
afterFloat | el primer bloc de la columna | Una línia de retícula entre una banda de flotants que encapçala la columna i el seu text, fins a maxLinesAfterFloat. |
looseParagraph | el paràgraf | El paràgraf recompost extraLines més llarg, amb el menor tracking que ho ha aconseguit (tracking; block.letterSpacing és el mateix valor en px). |
Els talls anivellats es registren a les columnes que tallen: column.bandCapped val true a tota columna tallada a nivell (la banda que deixa un bloc d'amplada de pàgina, una banda de tancament), i column.trailingCap marca a més la banda de tancament d'un capítol o del document (trailing).
import { buildDocument } from 'postext';
const doc = buildDocument(content, config);
for (const page of doc.pages) {
page.columns.forEach((column, i) => {
const levers: string[] = column.blocks.flatMap((block) => block.balancing?.levers ?? []);
if (column.trailingCap) levers.push('banda de tancament anivellada');
else if (column.bandCapped) levers.push('banda anivellada');
if (levers.length > 0) console.log(`pàgina ${page.index + 1}, columna ${i + 1}: ${levers.join(', ')}`);
});
}
// pàgina 3, columna 1: heading, heading
// pàgina 3, columna 2: listEnd, looseParagraph#Configuració per nivell
Cada nivell d'encapçalament pot sobreescriure els valors generals a través de l'array levels. Només fontSize (més el breakBefore d'H1, indicat a sota) difereix per defecte — totes les altres propietats s'hereten de la configuració general d'encapçalaments.
| Nivell | Mida de font per defecte | breakBefore per defecte |
|---|---|---|
| H1 | 18 pt | { enabled: true, parity: 'always-odd' } |
| H2 | 15 pt | { enabled: false, parity: 'any' } |
| H3 | 12 pt | { enabled: false, parity: 'any' } |
| H4 | 10 pt | { enabled: false, parity: 'any' } |
| H5 | 9 pt | { enabled: false, parity: 'any' } |
| H6 | 8 pt | { enabled: false, parity: 'any' } |
El valor per defecte d'H1 reprodueix la maqueta clàssica d'un llibre: cada encapçalament de nivell superior s'obre en una pàgina dreta (senar) nova, amb una pàgina separadora en blanc obligatòria que tanca el capítol anterior. Anul·la levels[0].breakBefore si el teu document és més pla que un llibre.
El breakBefore d'un nivell es combina camp a camp amb aquest valor per defecte, i un objecte headings que no el menciona el conserva. { parity: 'odd' } a H1 manté el salt i només canvia la paritat; { enabled: false } deixa que els capítols vagin seguits:
headings: {
fontFamily: 'Merriweather', // H1 continua saltant a una pàgina senar nova (always-odd)
levels: [{ level: 1, breakBefore: { parity: 'odd' } }], // …o: a una senar, sense pàgina en blanc obligatòria
}
headings: { levels: [{ level: 1, breakBefore: { enabled: false } }] } // capítols seguitsCanvia a postext 1.5. Fins a postext 1.4, qualsevol objecte headings desactivava el salt d'H1 llevat que es repetís levels[0].breakBefore, i un breakBefore parcial completava el camp que faltava amb el valor sense salt. Per això una configuració escrita en codi per a la 1.4 amb un objecte headings i sense salt d'H1 obre ara cada capítol en una pàgina senar nova, amb pàgines parelles en blanc on calgui. Perquè els capítols continuïn anant seguits, n'hi ha prou amb un camp a l'entrada d'H1 de levels:
headings: { fontFamily: 'Merriweather', levels: [{ level: 1, breakBefore: { enabled: false } }] } // com ho componia la 1.4On la configuració estava desada, el motor ho pot saber i ho fa per tu: el Sandbox amb els llibres i les configuracions que va desar llavors (vegeu Sandbox → Persistència), i openBundle / readBundle amb un paquet .postext escrit per postext 1.4 o anterior (vegeu Paquets escrits per postext 1.4 o anterior). Una configuració que vas desar tu pot passar per migrateConfig(config) de postext/bundle, que escriu els salts tal com els componia la 1.4 (pinLegacyHeadingBreaks): enabled: false en un H1 que no tenia salt, i parity: 'any' al costat d'un enabled: true que no indicava paritat, a H1 i als estils d'encapçalament. També fixa la mida de les fórmules, l'espai al voltant de les figures en línia (també als requadres), les marques en línia dels encapçalaments, la mida de les caplletres, el lloc sota una línia de dos punts que introdueix una llista, les línies que deixa d'un paràgraf o d'un element de llista el tall d'una caixa, els talls de línia després d'un guió llarg, la composició línia a línia del text en bandera, la divisió d'un paràgraf sota un encapçalament, els talls de línia després del guionet d'un compost i l'espai sota els contenidors :::paragraphs, tal com els componia la 1.4. Si li passes el markdown del llibre com a tercer argument, { content }, omet la fixació de les fórmules quan el text no té cap $, les dels buits quan cap línia no insereix un recurs (la del buit als requadres quan cap no ho fa dins d'un requadre), la de les marques quan cap encapçalament no en porta cap, la dels dos punts quan cap llista no segueix una línia que acaba en dos punts, la del tall de les caixes quan el text no obre cap :::callout, la dels guions llargs quan cap guió llarg no va entre paraules sense espais, la de la divisió sota un encapçalament quan el text no té cap encapçalament, la dels contenidors quan el text no obre cap contenidor :::paragraphs, i la dels compostos quan cap guionet no va entre dues lletres; la del text en bandera només s'aplica a una configuració que posa en bandera algun text corregut, i la dels contenidors només a una que declara algun estil de paràgraf (vegeu Paquets escrits per postext 1.4 o anterior). Per fixar només els salts, crida pinLegacyHeadingBreaks(config).
Les configuracions per nivell admeten les mateixes propietats que els valors generals — fontSize, lineHeight, fontFamily, color, fontWeight, marginTop, marginBottom, snapToGrid — més aquests camps exclusius de cada nivell:
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
italic | boolean | false | Renderitza l'encapçalament en cursiva. Es combina amb el fontWeight. |
textTransform | 'none' | 'uppercase' | 'none' | Passa a majúscules el títol de l'encapçalament (el prefix de numeració es conserva tal qual, igual que un chip o l'etiqueta d'una :ref que hi hagi). Conserva la longitud del text perquè el mapa d'origen de l'editor continuï sent 1:1: els caràcters la majúscula dels quals s'expandeix (ß → SS) es deixen com estan. El títol transformat alimenta també el marcador {titleText} dels dissenys avançats i les obertures de capítol. Els marcadors del PDF conserven el títol tal com es va escriure —Contribucions dels autors, no CONTRIBUCIONS DELS AUTORS—, igual que text-transform en CSS no toca el text en si (fins a postext 1.4 sortien en majúscules). |
letterSpacing | Dimension | 0 | Espaiat entre lletres després de cada glif de l'encapçalament —espais i prefix de numeració inclosos—, com letter-spacing en CSS. Un valor positiu obre les lletres (les versals compostes amb textTransform: 'uppercase' solen demanar-ne una mica: { value: 0.12, unit: 'em' }); un de negatiu estreny un cos de titular. Un valor en em és relatiu al fontSize del nivell. Les línies de l'encapçalament es mesuren amb aquest valor, de manera que tallen on acaba el text espaiat, i canvas, HTML i PDF el pinten igual. Una línia centrada o alineada a la dreta es col·loca per les seves lletres: l'espaiat que segueix el seu últim glif no compta, com en el text dels dissenys, així que un encapçalament centrat queda alineat amb l'obertura per defecte d'un encapçalament span: 'page'. Un nivell dibuixat amb el seu advancedDesign l'ignora, com els altres camps tipogràfics d'aquesta taula: cada element de text del disseny té el seu propi letterSpacing. Un estil d'encapçalament també el pot fixar, per als encapçalaments que l'usen. Fins a postext 1.4 els encapçalaments no tenien espaiat entre lletres, i la clau es descartava sense avís. |
numberingTemplate | string | '' | Plantilla del número automàtic del nivell. Un token … imprimeix el comptador acumulat d'aquest nivell d'encapçalament, opcionalment formatat amb un sufix — romans en majúscula, romans en minúscula, / alfabètic, amb zero a l'esquerra, en lletres (vint-i-u), com a ordinal (vint-i-unè), en numerals xinesos, xifra a xifra, en numerals financers i en cercle, o qualsevol nom de Grafies dels formats de numeració — i la resta del text és literal ('Capítol . ', '.', '第回' per a 第一百二十回; una barra inversa escapa una clau literal). Un token el comptador del qual encara és buit es plega juntament amb el separador contigu. Buida (el valor per defecte) vol dir sense número automàtic. El número resultant s'anteposa al títol en el flux, alimenta el marcador d'una ranura de disseny avançat (on el prefix no s'anteposa) i s'imprimeix a l'índex de continguts. Vegeu Números en lletres per a les paraules, i Estils d'encapçalament per donar a alguns encapçalaments d'un nivell una plantilla pròpia. |
numberSeparator | string | ' ' | El que separa el número del títol: a la columna, a l'obertura per defecte d'un nivell span: 'page', als titolets que imprimeixen la línia de l'encapçalament i als marcadors del PDF. El separador del nivell 1 uneix també el número i el títol d'una part a la pàgina de part i a la fila de part de l'índex de continguts quan no tenen disseny propi. Els capítols xinesos porten un espai ideogràfic o res (' ': 第一回 甄士隱夢幻識通靈). L'índex de continguts conserva la seva pròpia columna de números (toc.levels[].numberGap). Un estil d'encapçalament pot fixar el seu. Quan un títol es parteix en dos amb , com els títols aparellats de les novel·les xineses, les formes d'una sola línia (la columna, l'índex de continguts, els titolets) uneixen les dues meitats amb un espai ideogràfic si a banda i banda hi ha caràcters xinesos o japonesos, i amb un espai en els altres casos. |
numberPosition | 'before' | 'replace' | 'before' | On va el número generat. 'before': davant del títol, unit per numberSeparator. 'replace': el número és el títol sencer i el títol escrit al text font no s'imprimeix, de manera que # Nit amb numberingTemplate: 'الليلة {1:ordinal-feminine}' imprimeix الليلة الثانية. L'índex general llista el número com a títol de l'entrada (sense columna de número), i les capçaleres (, ) i els marcadors del PDF també el llegeixen; queda buit en aquest títol. Només per a títols numerats amb plantilla (la del nivell o la del seu estil); els altres conserven el seu títol. Un estil de títol pot fixar el seu, 'before' per conservar els títols que el seu nivell substituiria. |
breakBefore | HeadingBreakBeforeConfig | H1: { enabled: true, parity: 'always-odd' }H2–H6: { enabled: false, parity: 'any' } | Força un salt de pàgina abans de cada encapçalament d'aquest nivell. parity: 'odd' / 'even' restringeix a més en quin costat del plec s'obrirà — s'insereix una pàgina en blanc de farciment quan calgui (continua comptant per a la numeració). 'always-odd' / 'always-even' garanteixen a més almenys una pàgina separadora en blanc obligatòria entre el contingut anterior i el nou encapçalament (la separadora pertany al capítol anterior; qualsevol farciment de paritat addicional pertany al nou). Quan l'encapçalament és el primer bloc del document i la primera pàgina continua buida, la imposició de paritat s'omet — l'encapçalament aterra a la pàgina 1 tal qual. Un camp sense definir conserva el valor per defecte del nivell: { parity: 'odd' } a H1 continua saltant. |
hidden | boolean | false | Un encapçalament estructural: no imprimeix res ni ocupa espai a la columna ni dins d'una caixa :::callout — ni text, ni marges, ni banda d'obertura —, però fa tota la resta del que fa un encapçalament. El seu breakBefore continua obrint pàgina, obre la secció del seu estil, compta (llevat que el seu estil digui numbered: false) i apareix a :::toc, a les capçaleres amb i als marcadors del PDF. Per a una dedicatòria, una pàgina d'epígraf o un colofó que l'índex i els marcadors del lector necessiten, però que la pàgina no mostra. Millor en un estil d'encapçalament que en un nivell sencer; un encapçalament el sobreescriu amb / . |
headings: {
fontFamily: 'Merriweather',
levels: [
// Preajust canònic de llibre: els capítols s'obren a la pàgina dreta (senar).
{ level: 1, fontSize: { value: 24, unit: 'pt' }, breakBefore: { enabled: true, parity: 'odd' } },
{ level: 2, fontSize: { value: 18, unit: 'pt' }, italic: true },
]
}snapToGrid també funciona per nivell. Sense fixar, un nivell segueix headings.snapToGrid; fixat, el substitueix — així un mateix document pot deixar els seus H2 a línia i mitja del seu text, fora de la retícula fins al següent punt d'ajust, mentre els seus H3 arrodoneixen l'espai inferior a línies senceres de la retícula. Un estil d'encapçalament també el pot fixar, per als encapçalaments que l'usen.
headings: {
marginBottom: { value: 1.5, unit: 'em' },
levels: [
{ level: 2, snapToGrid: false }, // exactament 1,5 em sota cada H2
{ level: 3 }, // hereta headings.snapToGrid: true
],
}#Saltar abans
breakBefore és ortogonal als controls de numeració: activar-lo força un salt de pàgina, però el comptador numèric només es reinicia quan insereixes explícitament una directiva :::numbering. Les pàgines de farciment per paritat compten com a pàgines reals a la seqüència i reben capçaleres/peus segons les seves regles normals de senar/parell.
Un :::pagebreak just abans d'un d'aquests encapçalaments no substitueix el seu salt: l'encapçalament aplica igualment la seva paritat després de la pàgina que va obrir la directiva, cosa que pot afegir una pàgina en blanc. Vegeu Estils d'encapçalament per a un encapçalament que hagi de començar just després d'un salt manual.
Valors de paritat
| Valor | Comportament |
|---|---|
'any' (per defecte) | Sense restricció de paritat. L'encapçalament simplement s'obre a la pàgina següent. |
'odd' | Garanteix que l'encapçalament s'obri en una pàgina senar (costat dret). Només s'insereix una pàgina en blanc si la pàgina natural següent fos parella. |
'even' | Igual però per a una pàgina parella (costat esquerre). |
'always-odd' | Garanteix almenys una pàgina separadora en blanc obligatòria entre el contingut anterior i el nou encapçalament, i després força la paritat senar. Útil quan cada capítol ha de començar un plec nou. |
'always-even' | Igual però per a una pàgina parella. |
Pertinença de les pàgines en blanc
Les pàgines en blanc que insereix breakBefore reben la capçalera {chapterTitle} en funció del motiu de la inserció:
- Les pàgines inserides per satisfer una restricció de paritat (
'odd','even', o la cua de paritat de'always-*') pertanyen al capítol entrant. El seu marcador{chapterTitle}es resol al títol del nou capítol — perquè la pàgina en blanc només existeix per empènyer el nou capítol fins a la paritat correcta. - La pàgina separadora obligatòria que insereix
'always-odd'/'always-even'pertany al capítol anterior. És una pausa de tancament de capítol deliberada, així que{chapterTitle}continua mostrant el títol del capítol sortint.
Les capçaleres i la paleta d'una secció amb estil (vegeu Estils d'encapçalament) segueixen les mateixes dues regles a les pàgines en blanc.
Excepció a l'inici del document
Quan el primer bloc del document és un encapçalament amb breakBefore activat — o la font comença amb :::pagebreak —, la imposició de paritat s'omet mentre la primera pàgina continuï buida. L'encapçalament aterra a la pàgina 1 tal qual, independentment de la paritat configurada, de manera que un document que comença amb # Capítulo 1 amb parity: 'odd' no arrossega una pàgina en blanc inicial innecessària. Un cop s'ha col·locat qualsevol contingut, la imposició de paritat funciona de la manera habitual.
#Span i disseny avançat
Cada nivell d'encapçalament admet dos camps addicionals que controlen com es renderitza l'encapçalament com a obertura de capítol a pàgina completa.
| Propietat | Tipus | Predeterminat | Descripció |
|---|---|---|---|
span | 'column' | 'page' | 'column' | Quan és 'page', l'encapçalament es tracta com a obertura de capítol i el seu disseny avançat (si està habilitat) s'adjunta a la pàgina com una banda d'obertura per damunt del cos. Combina-ho amb breakBefore.enabled: true perquè l'obertura comenci de manera fiable en una pàgina nova. Sense un disseny propi, el títol el pinta una obertura per defecte a tota l'amplada de la caixa de text, amb la tipografia i l'interlineat del nivell i amb els seus trams en negreta, en cursiva, en superíndex i en subíndex (headings.inlineMarks), i la banda es mesura a aquesta amplada. La banda reserva totes les línies que pinta l'obertura: si aquesta ocupa més línies que la mesura del mateix encapçalament (un títol justificat els espais del qual cabrien estrets en una línia, un salt forçat ), la banda també les pren. Fins a postext 1.4 la banda es mesurava amb el títol ajustat a l'amplada de la columna, de manera que un títol que cap en una línia a l'amplada de la caixa ocupava una banda de dues línies, amb la línia centrada. Les altres columnes comencen sota la banda com la columna del mateix encapçalament. El text que n'obre una comença on començaria el text just a sota de l'obertura, vingui el que vingui després de l'obertura en la seva columna: el marginBottom de l'obertura per sota del seu títol o del seu disseny, portat a la línia següent de la retícula quan el nivell s'hi ajusta. Un encapçalament, una fórmula en bloc o una entrada de l'índex que n'obre una queda a l'altura del primer encapçalament o fórmula en bloc que hi ha sota l'obertura, amb els marges que hi té; si la columna de l'obertura continua amb una altra cosa, comença on comença aquest text. Un encapçalament ocult sota l'obertura no compta, i l'espai que l'equilibri de columnes afegeix damunt de l'encapçalament que hi ha sota l'obertura no es repeteix a les altres columnes. El que aquestes columnes ajusten a la retícula cau a la retícula de la pàgina. Fins a postext 1.4 el seu primer bloc quedava al peu de la banda: fora de la retícula quan la banda acabava entre dues línies, i enganxat a la banda, sense marge, quan l'obertura anava seguida d'un encapçalament (una línia més amunt que ara si la banda acabava a la retícula); a més, un encapçalament en aquest lloc perdia el marge superior que conservava el de la primera columna. |
advancedDesign | HeadingAdvancedDesignConfig | | Ranura de disseny de composició lliure per a aquest nivell. Quan està enabled, els elements de la ranura componen l'obertura. Fes servir dins d'un element de text per renderitzar el text del títol; , , etc. per inserir el número de l'encapçalament. |
advancedDesign.minHeight | Dimension | — | Alçada mínima reservada per a l'encapçalament en el flux de la columna. L'encapçalament ocupa max(fons del contingut del disseny, minHeight) i, a sota, el marginBottom de l'encapçalament (el del seu estil d'encapçalament, el del seu nivell o headings.marginBottom; 0,5 em del cos de l'encapçalament si cap no en fixa un altre); la suma s'arrodoneix cap amunt a la retícula de base quan els encapçalaments s'hi ajusten. Així una obertura pot empènyer el cos cap avall (o reclamar la pàgina sencera) encara que els seus elements siguin baixos o estiguin ancorats als marcs de pàgina/sang per damunt de l'encapçalament. Per a una franja d'exactament minHeight, posa aquest marginBottom a 0 i dona a minHeight un nombre enter de línies de la retícula. S'aplica sempre que enabled sigui true, fins i tot amb la ranura buida. Vegeu Alçada reservada per saber què compta en el fons del contingut del disseny. |
Una obertura tan alta com la pàgina. Quan l'alçada reservada arriba més enllà del peu de la columna — una portada el minHeight de la qual és l'alçada de la pàgina, una imatge o una caixa ancorada a la pàgina o a la sang que baixa fins al tall —, l'obertura reclama la resta de la pàgina: el text que la segueix comença a la pàgina següent, igual a totes les columnes d'una disposició a dues columnes o de columna i mitja. Per això una portada no necessita un :::pagebreak darrere (posar-lo no fa mal: no afegeix cap pàgina en blanc). Un encapçalament de columna (span: 'column') el disseny del qual és més alt que la seva columna reclama aquesta columna, i el text comença al capdamunt de la següent. El bloc de l'encapçalament baixa llavors fins al peu de la columna que reclama, i el seu disseny es compon contra aquesta banda: els elements ancorats a dalt es queden on són ancorats, i els que segueixen la banda —ancorats al seu centre o al seu peu, o amb alçada 'fill'— s'ajusten a l'espai que reclama l'encapçalament, igual que s'ajusten a l'alçada reservada quan hi cap (fins a postext 1.4 el bloc conservava l'alçada del seu text, de manera que aquests elements es componien contra una banda de l'alçada del títol, i el text que el seguia s'esmunyia sota el disseny). Els adorns de pàgina que no l'han de reclamar — una franja al llarg de tot el tall davanter, un ornament al peu de la pàgina — van al disseny de la capçalera o del peu: ancorats a 'page' o a 'bleed' i mostrats amb pages: 'opener', es pinten a la pàgina d'obertura sense reservar espai del cos.
Exemple — una obertura de capítol minimalista que mostra «Capítol N» damunt del títol:
{
"headings": {
"levels": [
{
"level": 1,
"span": "page",
"breakBefore": { "enabled": true, "parity": "always-odd" },
"advancedDesign": {
"enabled": true,
"slot": {
"elements": [
{
"kind": "text",
"id": "chapterLabel",
"placement": {
"anchor": { "to": "container", "edge": "top" },
"offset": { "y": { "value": 48, "unit": "pt" } },
"size": { "width": "fill" }
},
"content": "Capítol {numberRoman}",
"fontSize": { "value": 10, "unit": "pt" },
"align": "center",
"overflow": "ellipsis-end"
},
{
"kind": "text",
"id": "chapterTitle",
"placement": {
"anchor": { "to": "#chapterLabel", "edge": "below" },
"offset": { "y": { "value": 12, "unit": "pt" } },
"size": { "width": "fill" }
},
"content": "{titleText}",
"fontSize": { "value": 24, "unit": "pt" },
"fontWeight": 700,
"align": "center",
"overflow": "wrap",
"hyphenate": true
}
]
}
}
}
]
}
}Marcadors disponibles dins d'una ranura de disseny d'encapçalament:
{titleText}— text pla de l'encapçalament (sense prefix numèric). Un salt forçat al títol (\\) és aquí un salt de línia, tant en una banda d'obertura com en un disseny dins de la columna (fins a postext 1.4 un disseny dins de la columna imprimia un espai, tot i que la seva alçada es mesurava amb el salt). Les línies en què l'encapçalament ocult es reparteix a la seva columna es tornen a unir al títol tal com està escrit: una paraula tallada després del seu propi guionet conserva el guionet i no porta espai darrere, una paraula que la columna va dividir torna a quedar sencera, sense el guionet que hi va afegir el tall, un grup unit per un espai de no separació que no cabia a la columna i es va partir en aquest espai el recupera (Capítulo XVIII, noCapítuloXVIII), i cadascun dels altres talls retorna el seu espai. Fins a postext 1.4 cada salt de línia es convertia en un espai, de manera que un títol partit en un guionet imprimiaWord- Book, i en aquest títol es perdia un salt forçat.{number}— número formatat segonsnumberingTemplate.{numberDecimal},{numberRoman},{numberRomanLower},{numberAlpha},{numberAlphaLower}— el comptador de l'encapçalament (el compte acumulat del seu nivell, imprimeixi el que imprimeixi la plantilla) en altres formats de numeració: el tercer capítol dona3,III,iii,C,c, ambnumberingTemplateo sense. Un encapçalament sense numerar (un estil ambnumbered: false) els deixa buits.{numberWords},{numberWordsLower},{numberOrdinalWords},{numberOrdinalWordsLower}— el mateix comptador en lletres, amb majúscula inicial o en minúscula: Tres / tres, Tercero / tercero (vegeu Nombres en lletres).{numberHan}— el mateix comptador en numerals xinesos, en l'escriptura dellocaledel document: una obertura composta amb第{numberHan}回imprimeix 第十二回 al dotzè capítol mentre l'índex mostra12.{chapterNumber},{chapterTitle},{pageNumber},{totalPages},{bookTotalPages},{title},{subtitle},{author},{publishDate}— metadades compartides.{attr.<clave>}— un atribut escrit a la mateixa línia de l'encapçalament (# Título {author="I. Zango Martín"}), amb l'atribut de l'H1 del capítol actual com a alternativa. Els atributs absents es resolen com una cadena buida sense avís.
{chapterNumber} imprimeix el mateix que les capçaleres per a aquest capítol: el número de l'H1 quan el seu nivell (o el seu estil) té numberingTemplate; si no, l'ordinal del capítol —1, 2…, continuant després dels capítols maquetats abans que aquest—, i res per a un capítol sense numerar o un estil la plantilla del qual és ''. El disseny d'un encapçalament llegeix el capítol al qual pertany el seu encapçalament: un de nivell 1, el seu; un de nivell inferior, el de l'últim encapçalament de nivell 1 anterior. També és així quan dos capítols coincideixen en una pàgina, encara que les capçaleres d'aquesta pàgina imprimeixin el segon. L'alçada que reserva una obertura es mesura amb aquest mateix valor. Fins a postext 1.4 es mesurava amb el prefix numèric de l'encapçalament (buit sense plantilla), així que un disseny l'alçada del qual depengués de {chapterNumber} podia pintar-se més alt que l'espai que havia reservat; i el disseny imprimia el capítol de la pàgina, de manera que el primer de dos capítols que compartien pàgina mostrava el número del segon.
Els marcadors del comptador segueixen el llibre al llarg dels capítols compostos un a un (els comptadors que lliura continuationAfter()), i l'atribut startAt d'un encapçalament els reinicia (vegeu Format de document → Atributs d'encapçalament). Una obertura que diu Capítol III damunt d'un títol numerat 3. en el flux i a l'índex:
{
level: 1,
span: 'page',
numberingTemplate: '{1}.',
advancedDesign: {
enabled: true,
slot: { elements: [
{ kind: 'text', id: 'label', content: 'Capítol {numberRoman}', fontSize: { value: 10, unit: 'pt' }, overflow: 'ellipsis-end',
placement: { anchor: { to: 'container', edge: 'top-left' }, size: { width: 'fill', height: 'auto' } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontSize: { value: 24, unit: 'pt' }, overflow: 'wrap',
placement: { anchor: { to: '#label', edge: 'below' }, size: { width: 'fill', height: 'auto' } } },
] },
},
}Alçada reservada
Un encapçalament amb disseny avançat ocupa espai a la columna com qualsevol bloc: el text de cos que el segueix comença a sota d'aquest espai. L'espai és la més gran de tres alçades, totes mesurades cap avall des de la part superior de l'encapçalament (la part superior de l'àrea de contingut en una obertura que comença la seva pàgina):
- el mateix text de l'encapçalament, amb la tipografia del nivell (queda ocult sota el disseny, però conserva les seves línies);
- el fons del contingut del disseny: la vora inferior més baixa entre els elements que compten (vegeu més avall);
advancedDesign.minHeight.
A continuació s'hi suma el marginBottom de l'encapçalament (el del seu estil d'encapçalament, el del seu nivell o headings.marginBottom; 0,5 em si cap no en fixa un altre) i el resultat s'ajusta cap amunt a la retícula de base quan els encapçalaments s'hi ajusten. En una obertura (span: 'page') la mateixa franja queda lliure a totes les columnes de la pàgina. En un encapçalament de columna que no la desborda, el disseny es compon dins de la caixa de l'encapçalament, que té exactament aquesta alçada.
Quins elements compten. Compten tots els elements del disseny —textos, filets, caixes i imatges per igual (fins a postext 1.4 un element image no comptava mai, de manera que el text podia començar damunt d'una imatge de banda si minHeight no ho impedia)—, excepte:
- els que tenen
reserve: false: decoració que pot quedar sota el text; - els que segueixen la mateixa franja: els ancorats a la fila central del contenidor (
left,center,right) o a la inferior (bottom-left,bottom,bottom-right), els que tenen una alçada'fill'respecte al contenidor (una caixa o una línia vertical sense alçada l'omplen per defecte) i qualsevol element ancorat a un d'ells. El contenidor és la franja reservada, així que aquests elements es recolzen al seu peu o l'abasten: una línia sota la franja, un plafó de color darrere del títol. Segueixen l'alçada; mai no la fixen. Un text d'entre ells que conserva la seva pròpia alçada sí que necessita lloc: un títol ancorat al peu de la franja fa que la franja mesuri almenys el que fa el títol, de manera que mai no comença per damunt de la vora superior de l'encapçalament. AmbminHeight: 36mmi el títol ancorat abottom-left, la caixa de l'encapçalament fa 36 mm més el seumarginBottom, ajustada cap amunt a la retícula, i el títol queda al peu d'aquesta caixa, just damunt del text que segueix; senseminHeight, un títol que ocupa més línies que el text del mateix encapçalament fa la franja tan alta com el títol. Fins a postext 1.4 aquest títol es pintava cap amunt, damunt del text que precedeix l'encapçalament. Les caixes, els filets i les imatges que segueixen la franja no fixen aquest mínim, així que un plafó ancorat al peu pot sobresortir per damunt de l'encapçalament.
Els elements ancorats a la pàgina o a la sang compten segons quant baixin per sota de la part superior de l'encapçalament. Una banda al capdamunt de la pàgina que acaba per damunt de l'encapçalament no compta gens; una imatge a sang que el sobrepassa empeny el text fins a la seva vora inferior. El mateix passa amb tot el que és a baix de la pàgina: un segell a 25 mm del peu, una banda lateral a tota alçada o un marc reserven la pàgina fins a la seva vora inferior, i el text sol començar a la pàgina següent. Marca aquesta decoració amb reserve: false —es continua pintant i altres elements s'hi poden continuar ancorant— i dona a l'encapçalament l'espai que necessita amb els seus elements de text o amb minHeight:
{
"kind": "image", "id": "seal", "resourceId": "seal", "reserve": false,
"placement": {
"anchor": { "to": "page", "edge": "bottom-right" },
"offset": { "x": { "value": -25, "unit": "mm" }, "y": { "value": -25, "unit": "mm" } },
"size": { "width": { "value": 30, "unit": "mm" } }
}
}Un element ancorat a un altre que no reserva continua comptant tret que també el marquis (un rètol compost damunt del segell necessita el seu propi reserve: false).
On es pinta. El disseny d'una obertura (span: 'page') es dibuixa abans que el cos, així que la decoració que no reserva espai queda sota el text. El disseny d'un encapçalament de columna es dibuixa amb el bloc de l'encapçalament —damunt dels blocs que el precedeixen a la columna i sota dels que el segueixen— allà on estigui col·locat per damunt del peu de la seva columna: també als marges laterals, al marge superior, a la sang i a les columnes veïnes. El peu de la columna el talla, al llenç i al PDF, perquè allà acaba el flux (vegeu Més alt que la columna més avall). Fins a postext 1.4 el llenç i el PDF també el tallaven a la part alta de la seva columna, de manera que una banda ancorada a la part alta de la pàgina o de la sang entrava als marges laterals però s'aturava al marge superior. La decoració ancorada al peu de la pàgina va en una obertura, o al disseny del peu amb pages: 'opener'.
Més alt que la columna. Quan l'espai arriba més enllà del peu de la columna —un minHeight tan alt com la pàgina, una imatge o un marc que baixen fins al tall—, l'encapçalament reclama la resta de la seva pàgina (una obertura, a totes les seves columnes) o de la seva columna (un encapçalament de columna), i el text que el segueix comença a la pàgina o a la columna següent. El seu bloc arriba llavors fins al peu de la columna, sense retallar-se mai a l'alçada del seu text, de manera que la banda contra la qual es compon el seu disseny és l'espai que reclama (minHeight inclòs, fins al peu). Un disseny més alt que la mateixa pàgina —una entradeta llarga en una pàgina de pantalla petita— continua tallant-se al peu de la pàgina (un disseny de columna, al de la seva columna): el Sandbox ho assenyala al tauler Revisió com a Disseny de títol tallat. La maquetació no emet cap avís per això —una portada que reclama la seva pàgina és el més habitual i no perd res—, però qualsevol amfitrió pot fer la mateixa comprovació sobre la maquetació acabada: collectHeadingDesignCuts(doc) retorna un { kind: 'headingDesignCut', pageIndex, level, where, overflowPx, sourceStart, sourceEnd } per cada encapçalament el text de disseny del qual queda més enllà del peu del tall de la pàgina (where: 'page', una obertura) o de la seva columna (where: 'column'), i formatWarning en descriu cadascun. Vegeu Una obertura tan alta com la pàgina a Span i disseny avançat.
La columna lateral. En una disposició de columna i mitja la columna lateral de la qual acull flotants (sideColumnRole: 'floats'), un element del disseny d'un encapçalament de columna que cau a la columna lateral —un numeral de capítol ancorat a la pàgina a la columna del marge exterior, a l'obertura d'un llibre de text— manté la pila lateral apartada. Cada figura, taula o requadre amb span: 'side' que la pàgina col·loca després de l'encapçalament deixa un buit de flotant lliure al voltant de cadascun d'aquests elements: es queda on el posa la pila quan hi cap per damunt de l'element i, si no, passa per sota —o espera a la pàgina següent quan la resta de la columna lateral no el pot acollir allà—. Així, un numeral al capdamunt del canal manté tota la pila per sota, mentre que un número de secció penjat al marge al costat d'un encapçalament més avall de la pàgina deixa el capdamunt del canal a les figures que cita la pàgina (la figura marginal continua al capdamunt de la seva pàgina). El que la columna lateral ja acull quan es col·loca l'encapçalament no es mou: una figura apilada abans a la pàgina que arriba fins a l'element de l'encapçalament es queda on és, a sota de l'element; per això, si el disseny té un element a la columna lateral a mitja pàgina, convé citar-ne les figures després de l'encapçalament. Els elements amb reserve: false deixen lliure la columna lateral, igual que deixen lliure el text. Una obertura (span: 'page') no necessita res d'això: la seva banda es reserva a totes les columnes, la lateral inclosa. Fins a postext 1.4 una figura lateral citada en una obertura així es col·locava al capdamunt de la columna lateral, damunt del numeral.
Portades. Per això un encapçalament de portada que omple la seva pàgina —amb un minHeight tan alt com la pàgina, o amb una imatge a pàgina completa al seu disseny— envia tot sol el text que el segueix a la pàgina següent, tant en disposicions d'una columna com de diverses. Un :::pagebreak just darrere és opcional i no fa mal: un salt de pàgina en una pàgina encara buida no fa res, així que mai no afegeix una pàgina en blanc. Només cal quan el disseny de la portada acaba abans del peu de la pàgina i tot i així el text ha de començar en una pàgina nova.
# Memòria anual 2026 {style="portada"}
:::pagebreak
# Carta de la presidenta#Nombres en lletres
Dos sufixos de les plantilles de numeració escriuen un comptador en lletres, en la llengua del document (el locale de primer nivell o, si no està definit, la llengua de partició — vegeu Llengua del document): words per al cardinal i ordinal per a l'ordinal. La caixa (majúscula o minúscula) del sufix fixa la de les paraules, com fan A / a amb les lletres:
| Token | Anglès (21) | Espanyol (21) | Xinès (21) |
|---|---|---|---|
| twenty-one | veintiuno | 二十一 |
| Twenty-one | Veintiuno | 二十一 |
| TWENTY-ONE | VEINTIUNO | 二十一 |
| twenty-first | vigesimoprimero | 第二十一 |
| Twenty-first | Vigesimoprimero | 第二十一 |
| TWENTY-FIRST | VIGESIMOPRIMERO | 第二十一 |
S'escriuen en lletres l'anglès, l'espanyol, el xinès i l'àrab; qualsevol altra llengua pren les paraules en anglès, com fan les cadenes integrades de continuació de taules. El xinès escriu els numerals informals de simp-chinese-informal o trad-chinese-informal, segons l'escriptura de locale (一万 / 一萬), amb 第 davant de l'ordinal; els caràcters han no tenen caixa, així que les tres grafies d'un sufix imprimeixen el mateix. L'anglès segueix l'ús nord-americà (one hundred five, sense and). L'espanyol fa servir les formes masculines, com es numera un capítulo o un libro (capítulo primero, tercero, veintiuno), i escriu els ordinals del 13 al 29 en una sola paraula, com prefereix la RAE (decimotercero, vigesimoprimero). Els cardinals s'escriuen fins a 999 999 i els ordinals espanyols fins a 999; els nombres més grans s'imprimeixen en xifres.
Els nombres àrabs concorden en gènere amb el substantiu que compten, així que el sufix admet un modificador: -feminine (o -f) per a un substantiu femení, -masculine (-m, el valor per defecte) per a un de masculí; -classical escriu les centenes مائة, com les edicions de Bulaq i la majoria de les egípcies, en lloc del modern مئة. {1:ordinal} escriu l'ordinal definit en nominatiu que fa servir un títol: الفصل {1:ordinal} dona الفصل الأول, الفصل الحادي عشر, الفصل الحادي والعشرون; الليلة {1:ordinal-feminine} dona الليلة الأولى, الليلة الحادية عشرة, الليلة الحادية والعشرون, الليلة المئتان i, per damunt de cent, la fórmula clàssica de «després de»: الليلة الخامسة والأربعون بعد الثلاثمئة, الليلة الحادية بعد الألف. {1:words} escriu el cardinal (واحد وعشرون; en femení إحدى عشرة, واحدة وعشرون). Els ordinals s'escriuen en lletres fins a 9 999 i els cardinals fins a 99 999; els modificadors es combinen ({1:ordinal-f-classical}) i les altres llengües els ignoren. L'àrab no té majúscules, així que la caixa del sufix no canvia res. Els marcadors de disseny {numberWords} i {numberOrdinalWords} escriuen les formes masculines; per a una obertura en femení, posa l'ordinal a la plantilla del nivell i imprimeix-lo amb {number}.
En un disseny d'encapçalament, {numberWords} / {numberWordsLower} i {numberOrdinalWords} / {numberOrdinalWordsLower} escriuen el comptador de l'encapçalament de la mateixa manera, així l'obertura pot dir Capítulo uno mentre l'índex mostra 1. El textTransform: 'uppercase' d'un element de text dona les majúscules:
// Novel·la en espanyol: «CAPÍTULO PRIMERO» damunt del títol, «1.» a l'índex.
{ level: 1, numberingTemplate: '{1}.', span: 'page',
advancedDesign: { enabled: true, slot: { elements: [
{ kind: 'text', id: 'n', content: 'Capítulo {numberOrdinalWordsLower}', textTransform: 'uppercase', /* … */ },
{ kind: 'text', id: 't', content: '{titleText}', /* … */ },
] } } }#Llistes no ordenades
La propietat unorderedLists controla com es renderitzen les llistes amb pics (-, *, +) i les llistes de tasques d'estil GFM (- [ ], - [x]). S'admeten fins a cinc nivells d'imbricació.
#Valors per defecte de les llistes no ordenades
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily | string | hereta bodyText.fontFamily | Font del text dels elements. |
color | ColorValue | Color principal (#295AA3) | Color del text i dels pics dels elements. Enllaçat a l'entrada main-color de la paleta per defecte. |
fontWeight | number | 700 | Pes del text dels elements (100–900). Els pics hereten aquest pes tret que se sobreescrigui per nivell. |
italic | boolean | false | Renderitza el text dels elements en cursiva. |
bulletChar | string | '•' | Glif utilitzat com a pic. |
bulletFontSize | Dimension | 1 em | Mida del glif del pic. Les unitats relatives s'escalen amb la mida del cos de text. |
gap | Dimension | 0.5 em | Espai horitzontal entre el pic i el text de l'element. |
indent | Dimension | 0 em | Sagnat base per al nivell 1. Els nivells més profunds s'encadenen des de l'inici del text del nivell anterior tret que s'especifiqui (vegeu més avall). |
bulletVerticalOffset | Dimension | 0 em | Ajust fi vertical del pic. Els valors negatius el pugen; els positius l'abaixen. |
marginTop / marginBottom | Dimension | 1.5 em | Espai abans i després del bloc de llista. |
itemSpacing | Dimension | 0 em | Espai vertical extra entre elements, afegit a l'alçada de línia. Al voltant d'una llista imbricada en un element d'una altra s'aplica, a tots dos costats, el de la llista exterior: abans del primer element de la llista imbricada i després de l'últim (fins a postext 1.4, l'element que seguia una llista imbricada prenia l'espai d'aquesta). |
snapTopToGrid | boolean | false | Arrodoneix cap amunt l'espai damunt de la llista perquè el seu primer element quedi a la retícula de base, com el text sota un encapçalament; marginTop passa a ser un mínim. El final de la llista retorna el flux a la retícula en qualsevol cas, així que amb itemSpacing a 0 tots els elements queden alineats amb el text de la columna contigua. Desactivat per defecte, com fins a postext 1.4: un marginTop que no és un nombre enter de línies deixa els elements fora de la retícula fins que acaba la llista. No afecta les llistes dins de requadres, l'interior dels quals queda fora de la retícula. |
hangingIndent | boolean | true | Quan està actiu, les línies ajustades s'alineen amb el primer caràcter de text en lloc de fer-ho sota el pic (sagnat francès). |
levels | UnorderedListLevelConfig[] | — | Sobreescriptures per profunditat per als nivells 1–5. Vegeu més avall. |
#Extensions per a llistes de tasques
Els elements de tasca GFM (- [ ] …, - [x] …) es renderitzen com a elements de llista no ordenada substituint el pic per una casella. Els camps següents només s'apliquen a les tasques:
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
taskCheckboxChar | string | '☐' | Glif per a tasques no marcades. |
taskCheckedChar | string | '☑' | Glif per a tasques completades. |
taskCompletedStrikethrough | boolean | true | Dibuixa una línia que ratlla el text de les tasques completades. |
taskCompletedColor | ColorValue | hereta el color de l'element | Color opcional aplicat al text de les tasques completades. Quan s'omet, es fa servir el color habitual de l'element. |
#Sobreescriptures per nivell (llistes no ordenades)
Cada entrada de levels apunta a una profunditat (1–5) i pot sobreescriure qualsevol de les propietats següents:
| Propietat | Tipus | Descripció |
|---|---|---|
bulletChar | string | Glif de pic per a aquesta profunditat. |
fontFamily | string | Font del text en aquesta profunditat. |
fontSize | Dimension | Mida del glif de pic en aquesta profunditat. |
color | ColorValue | Color del text de l'element. |
fontWeight | number | Pes del text de l'element. |
italic | boolean | Activa la cursiva. |
indent | Dimension | Sagnat explícit del pic en aquesta profunditat. Vegeu la regla d'encadenament a continuació. |
verticalOffset | Dimension | Ajust fi vertical del pic en aquesta profunditat. |
Encadenament de sagnats. El nivell 1 arrenca sempre amb l'indent general (per defecte 0 em — els pics queden enganxats a la vora de la columna). Per als nivells 2–5, si deixes indent sense definir, el motor col·loca el pic a l'inici de text del nivell anterior (sagnat del pare + amplada del pic + gap). Defineix un indent explícit en un nivell per trencar l'encadenament i fixar aquesta profunditat on prefereixis.
unorderedLists: {
bulletChar: '—',
gap: { value: 0.4, unit: 'em' },
hangingIndent: true,
levels: [
{ level: 2, bulletChar: '·' },
{ level: 3, bulletChar: '◦', color: { hex: '#666666', model: 'hex' } },
],
}#Llistes ordenades
La propietat orderedLists controla les llistes numerades (1., 2), etc.). S'admeten fins a cinc nivells d'imbricació i cada profunditat pot fer servir un format de numeració diferent.
#Valors per defecte de les llistes ordenades
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily | string | hereta bodyText.fontFamily | Font del text i del marcador numèric. |
color | ColorValue | Color principal (#295AA3) | Color del text i del marcador numèric. Enllaçat a l'entrada main-color de la paleta per defecte. |
fontWeight | number | 700 | Gruix del text i del marcador numèric (100–900). |
italic | boolean | false | Mostra el text en cursiva. |
numberFormat | OrderedListNumberFormat | 'arabic' | Estil de numeració: 'arabic', 'lower-alpha', 'upper-alpha', 'lower-roman', 'upper-roman'. També valen les grafies de les altres opcions ('decimal', 'roman-lower', 'i'…; consulta Grafies dels formats de numeració); un valor desconegut numera en aràbics i s'avisa. |
prefix | string | '' | Text que precedeix el número, amb l'estil del separador: amb '(' aquí i ')' com a separador, una llista xinesa es numera (一), (二). Quan el separador es dibuixa com un tram propi, el prefix també, just abans del número. |
separator | string | '.' | Caràcter situat entre el número i el text — normalment '.' o ')'. |
separatorFontFamily | string | hereta fontFamily | Font del separador. Quan algun estil del separador difereix del del número, el separador es dibuixa com un tram propi després del número (alineat a la dreta) — p. ex. 1 en Optima Bold negre seguit de • en DIN Pro Bold blau. |
separatorFontWeight | number | hereta fontWeight | Gruix del separador (100–900). |
separatorItalic | boolean | hereta italic | Mostra el separador en cursiva. |
separatorColor | ColorValue | hereta color | Color del separador. Es respecten les referències a la paleta. |
separatorGap | Dimension | 0 em | Espai entre el número i el separador. El text de l'element continua començant gap després del separador. |
numberFontSize | Dimension | 1 em | Mida del marcador numèric. |
gap | Dimension | 0.5 em | Espai horitzontal entre el número i el text de l'element. |
indent | Dimension | 0 em | Sagnat base per al nivell 1; els nivells més profunds s'encadenen des de l'inici del text del nivell anterior, tret que s'especifiqui una altra cosa. |
numberVerticalOffset | Dimension | 0 em | Ajust fi vertical del marcador numèric. |
marginTop / marginBottom | Dimension | 1.5 em | Espai abans i després del bloc de llista. |
itemSpacing | Dimension | 0 em | Espai vertical addicional entre elements. Al voltant d'una llista niada dins d'un element d'una altra s'aplica, a tots dos costats, el de la llista exterior: abans del primer element de la llista niada i després de l'últim (fins a postext 1.4, l'element que seguia una llista niada prenia l'espai d'aquesta). |
snapTopToGrid | boolean | false | Arrodoneix cap amunt l'espai sobre la llista perquè el seu primer número quedi a la retícula de base, com el text sota un encapçalament; marginTop passa a ser un mínim. El final de la llista torna el flux a la retícula en qualsevol cas, de manera que amb itemSpacing a 0 tots els elements queden alineats amb el text de la columna del costat. Desactivat per defecte, com fins a postext 1.4: un marginTop que no és un nombre enter de línies deixa els elements fora de la retícula fins que acaba la llista. No afecta les llistes dins de requadres, l'interior dels quals queda fora de la retícula. |
numberWidth | 'run' | 'level' | 'run' | L'amplada de la columna de números d'un element, que fixa on comença el seu text; els números s'alineen a la dreta dins d'aquesta columna. 'run': el número més ample del tram mateix de l'element, és a dir, els elements d'una mateixa profunditat sense res més que elements més profunds entre ells. Una figura, un paràgraf o un requadre entre dos elements obre un tram nou, de manera que ii) després d'una taula pot començar el text una mica més a la dreta que i) abans de la taula, i una llista de nou elements compon el text més a l'esquerra que una de dotze. 'level': el número més ample d'aquella profunditat en tot el document (el capítol, en un llibre), de manera que totes les llistes, i cada part d'una llista interrompuda, comencen el text al mateix lloc, com ja fan els sagnats dels nivells més profunds. |
hangingIndent | boolean | true | Les línies de continuació s'alineen amb el primer caràcter del text, no sota el número. |
levels | OrderedListLevelConfig[] | — | Sobreescriptures per profunditat per als nivells 1–5. |
#Sobreescriptures per nivell (llistes ordenades)
Cada entrada de levels pot sobreescriure numberFormat, prefix, separator, fontFamily, fontSize, color, fontWeight, italic, indent, verticalOffset i l'estil del separador (separatorFontFamily, separatorFontWeight, separatorItalic, separatorColor, separatorGap) — s'hi aplica el mateix encadenament de sagnats que a les llistes no ordenades. L'estil del separador d'un nivell hereta l'estil del seu propi número, tret que s'indiqui l'opció general del separador.
Alineació a la dreta. El pipeline mesura el número formatat més ample dins de cada tram i sagna tots els elements d'aquest tram perquè els marcadors quedin alineats per la vora dreta. Una llista de deu elements mostrada com 1. – 10. desplaça els números d'una xifra cap a la dreta perquè el separador caigui sempre a la mateixa columna.
orderedLists: {
numberFormat: 'arabic',
separator: '.',
levels: [
{ level: 2, numberFormat: 'lower-alpha' },
{ level: 3, numberFormat: 'lower-roman', separator: ')' },
],
}Això produeix la clàssica combinació niada:
1. Primer element
a. Subelement
i) Nota profunda
b. Subelement
2. Segon element
La jerarquia dels documents xinesos (GB/T 15834—2011, annex B.3) té cinc nivells: 一、, després (一), després 1., després (1) i després ①:
orderedLists: {
levels: [
{ level: 1, numberFormat: 'simp-chinese-informal', separator: '、' },
{ level: 2, numberFormat: 'simp-chinese-informal', prefix: '(', separator: ')' },
{ level: 3, numberFormat: 'arabic', separator: '.' },
{ level: 4, numberFormat: 'arabic', prefix: '(', separator: ')' },
{ level: 5, numberFormat: 'circled-decimal', separator: '' },
],
}#Matemàtiques
La propietat math controla com s'analitzen i es mostren les fórmules LaTeX escrites entre delimitadors $...$ (en línia) i $$...$$ (en bloc). El motor subjacent és MathJax (el paquet mathjax-full) en mode de sortida SVG, rasteritzat al canvas i incrustat com a glifs escalables al PDF.
interface MathConfig {
enabled?: boolean; // Mostrar el LaTeX. Si és false, els spans surten com a TeX literal.
fontSizeScale?: number; // × la mida del text que envolta la fórmula (la del cos en les fórmules en bloc).
color?: ColorValue; // Color de la fórmula; hereta el del cos si s'omet.
marginTop?: Dimension; // Espai damunt de les fórmules en bloc.
marginBottom?: Dimension; // Espai mínim a sota; l'ajust a la retícula el pot augmentar.
indentAfterDisplay?: boolean; // Sagnar el paràgraf que segueix una fórmula en bloc.
keepWithLeadIn?: boolean; // Mantenir una fórmula en bloc amb la línia que la introdueix.
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
enabled | boolean | true | Quan és false, els spans $...$ i $$...$$ encara s'analitzen (els avisos per delimitadors sense tancar es continuen emetent), però es mostren com el seu codi TeX literal. Útil quan el contingut conté signes de dòlar a propòsit o quan vols desactivar del tot la representació matemàtica. |
fontSizeScale | number | 1.0 | Multiplicador aplicat a la mida del text que envolta la fórmula abans de representar-la: un em de la font TeX de la fórmula mesura aquesta mida × fontSizeScale — bodyText.fontSize per a una fórmula en bloc i per a les fórmules en línia del text de cos, la mida del bloc que la conté per a una fórmula en línia en un encapçalament, un estil de paràgraf, un peu o el cos d'un requadre. 1.0 la iguala al text que l'envolta; els valors entre 0.9 i 1.1 són habituals quan la font matemàtica sembla lleugerament més gran o més petita que la font del text. Canvia a postext 1.5: fins a la 1.4 les fórmules sortien al voltant d'un 13 % més grans (consulta més avall). |
color | ColorValue | hereta el del cos | Color de la fórmula representada. Omet-lo per heretar bodyText.color. Fixa'l explícitament quan vulguis tenyir les fórmules d'una manera diferent del text — per exemple perquè coincideixin amb l'accent dels encapçalaments. |
marginTop | Dimension | 0.8em | Espai per sobre d'una fórmula en bloc. S'ignora en les fórmules en línia. |
marginBottom | Dimension | 0.8em | Espai per sota d'una fórmula en bloc. És un mínim: l'ajust a la retícula el pot ampliar perquè la línia de base següent caigui en una línia de la retícula (tant si page.baselineGrid dibuixa la retícula com si no). |
indentAfterDisplay | boolean | true | Sagna la primera línia del paràgraf que segueix una fórmula en bloc, com qualsevol altre paràgraf. Amb false, tot paràgraf just després d'una fórmula en bloc es compon sense sagnat, com a continuació de la frase que la fórmula ha interromput («on L és…»). Després d'una fórmula escrita dins d'un paràgraf —sense cap línia en blanc ni a sobre ni a sota— el text no se sagna mai: el que s'escriu sota el seu $$ de tancament continua aquell paràgraf (consulta Fórmules matemàtiques). |
keepWithLeadIn | boolean | false | Manté una fórmula en bloc a la columna de la línia que la introdueix, com la penalització \predisplaypenalty de TeX. Quan la fórmula no cap sota l'última línia del paràgraf anterior, aquesta línia passa amb la fórmula a la columna o pàgina següent; quan les línies que quedarien enrere són menys de bodyText.widowMinLines (menys d'una si bodyText.avoidWidows està desactivat), un paràgraf que comença en aquella columna passa sencer (amb els encapçalaments que tanquen la columna per damunt, segons headings.keepWithNext). La línia que passa queda sola a dalt de tot de la columna següent, digui el que digui la regla de les òrfenes. Amb false, la fórmula passa sola, i la línia que la introdueix pot tancar la columna de dalt o quedar sobre una figura que encapçala la següent. |
math: {
enabled: true,
fontSizeScale: 1.0,
color: { hex: '#295AA3', model: 'hex' },
marginTop: { value: 1, unit: 'em' },
marginBottom: { value: 1, unit: 'em' },
}Mida de les fórmules, canvia a postext 1.5. MathJax dona la caixa de cada fórmula en ex, i un ex de la seva font TeX mesura 0,442 em. Fins a postext 1.4 el motor el prenia per mig em, de manera que cada fórmula es componia al voltant d'un 13 % més gran que bodyText.fontSize × fontSizeScale. Ara les fórmules surten de la mida documentada, i les línies i pàgines amb matemàtiques es recomponen. Una configuració escrita en codi per a la 1.4 conserva la mida de les fórmules de la 1.4 si passa per pinLegacyMathSize, de postext/bundle, una sola vegada i tal com està escrita:
import { pinLegacyMathSize } from 'postext/bundle';
config = pinLegacyMathSize(config); // fórmules, i l'espai al voltant de les fórmules en bloc, com les componia la 1.4Hi ha altres canvis de regles a la 1.5 que també poden moure les teves pàgines: els salts dels encapçalaments, l'espai al voltant d'una figura en línia (en el text corregut i en els requadres), les marques en línia dels encapçalaments, la mida de les caplletres, el lloc que es deixa sota una línia acabada en dos punts per a la llista que introdueix, les línies que el tall d'una caixa deixa d'un paràgraf o d'un element de llista, els salts de línia després d'un guió llarg, la composició del text en bandera, la divisió d'un paràgraf sota un encapçalament, els salts de línia després del guionet d'un compost i l'espai sota un contenidor :::paragraphs. migrateConfig, aplicada una sola vegada amb el markdown que compon la configuració, fixa els que aquest text necessita, també la mida de les fórmules (consulta Paquets escrits per postext 1.4 o anterior):
import { migrateConfig } from 'postext/bundle';
config = migrateConfig(config, undefined, { content: markdown }); // salts d'encapçalament, fórmules, espais en línia, marques dels encapçalaments, caplletres, dos punts, talls de les caixes, guions llargs, compostos, text en bandera, divisions sota un encapçalament i espai sota els contenidors com els componia la 1.4Així s'evita el que moverien aquests canvis de regles, però no es conserven totes les pàgines de la 1.4. La 1.5 corregeix a més errors de composició, i una correcció no té fixació: una configuració antiga la rep igual que una de nova, de manera que una pàgina afectada encara es pot moure. Entre aquestes correccions: un encapçalament a tota la pàgina sense disseny propi es mesura a l'amplada de la pàgina i es compon amb l'interlineat del seu nivell; en un disseny d'encapçalament no es reserva res sota la línia de base d'una caplletra; una caplletra pren el color de la paleta de la secció i es compon també en un text de disseny el overflow del qual no és 'wrap', que llavors salta de línia; un paràgraf dins d'una caixa pinta el tracking amb què es va mesurar; una caixa partida conserva la columna de la icona a cada fragment; una línia d'una caixa que estiraria els espais més de 3× es compon en bandera, com en el text corregut; la palanca dels paràgrafs fluixos no deixa mai una línia justificada més ampla del que permet maxWordSpacing; una caixa flotant conserva un marginBottom (en una banda superior) o un marginTop (en una banda inferior) més gran que l'espai dels flotants; un text de disseny centrat o alineat a la dreta amb tracking (un titolet, el títol d'una obertura) es col·loca per les seves lletres, sense el tracking que segueix l'última; sota una obertura a tota la pàgina, el text que obre la segona columna comença on començaria el text just a sota de l'obertura, també quan l'obertura va seguida d'un encapçalament; la línia de punts de l'índex s'atura abans del número de pàgina en una font el kerning de la qual separa una sèrie de punts; un titolet llegeix l'encapçalament tal com està escrit, sense espai on una de les seves línies acaba després d'un guionet o un guió llarg o dins d'una paraula tallada per l'amplada (MEDIOAMBIENTALES, no MEDIOAMBIENTALE S), i el {titleText} del disseny d'un encapçalament el llegeix igual (thousand-colour, no thousand- colour); un text ancorat al peu o al centre de la franja d'un disseny d'encapçalament manté la franja prou alta per contenir-lo, de manera que ja no puja sobre el text que precedeix l'encapçalament; una paraula més ampla que la seva línia, tallada al costat d'un guionet que ja porta, es talla darrere d'aquest guionet i no en rep cap altre; l'element que segueix una llista niada dins d'una altra pren l'itemSpacing de la seva pròpia llista, no el de la niada; i la resta d'una paraula tallada per ser més ampla que la seva línia conserva els seus propis punts de tall, de manera que una adreça web continua tallant-se per les juntures i un compost pels guionets en lloc de per les síl·labes del diccionari.
pinLegacyMathSize multiplica l'escala per 1,1312 (0,5 ÷ 0,442) i divideix pel mateix factor els marges de les fórmules en bloc en em. Amb l'escala sola no n'hi ha prou en un llibre amb fórmules en bloc: els seus marges són mesures de la mida de la fórmula mateixa, de manera que creixerien el mateix 13 % i empènyerien cap avall el text que les segueix. Escrits per als marges per defecte, els tres valors són:
math: {
fontSizeScale: 1.131,
marginTop: { value: 0.7072, unit: 'em' },
marginBottom: { value: 0.7072, unit: 'em' },
} // fórmules tan grans com les componia la 1.4, amb l'espai que els deixava al voltantOn la configuració estava desada, el motor ho pot saber i ho fa per tu: openBundle / readBundle amb un paquet .postext escrit abans de la 1.5 (consulta Paquets escrits per postext 1.4 o anterior), i el Sandbox amb els llibres, la configuració de treball i els fitxers postext-config.json que va desar llavors (consulta Sandbox → Persistència). Es llegeixen a través de migrateConfig, que fixa la mida (pinLegacyMathSize): fontSizeScale passa a ser l'escala desada (1 si no n'hi havia) × 1,1312, i un marge de les fórmules en bloc en em o rem — una mesura de la mida de la fórmula mateixa — es divideix pel mateix factor, de manera que l'espai al voltant d'una fórmula en bloc queda com el deixava la 1.4 (els 0,8 em per defecte passen a 0,7072 em). Un marge en una unitat de pàgina (pt, mm…) es queda com està, i també una configuració amb enabled: false o una el llibre de la qual no té cap $. Llavors el llibre antic es compon com el componia la 1.4, i la seva secció math mostra la mida amb què es compon. Per compondre aquell llibre a la mida actual, restableix aquests valors: Fórmules → Escala de mida, Marge superior de les fórmules en bloc i Marge inferior de les fórmules en bloc al Sandbox, o en codi:
math: { ...config.math, fontSizeScale: 1, marginTop: undefined, marginBottom: undefined } // mida i marges actualsEquacions numerades. Una fórmula en bloc amb \tag{…} ocupa tota la seva mesura (la columna, o l'amplada interior del requadre que la conté): l'equació va centrada i el seu número alineat a la dreta, a la línia de l'equació, a cada fila etiquetada d'un align. \tag*{…} imprimeix l'etiqueta tal qual, sense parèntesis. Només les etiquetes explícites imprimeixen número; entorns com equation no es numeren per si sols. Una equació numerada més ampla que la seva mesura desborda per la dreta, com qualsevol fórmula en bloc. (Fins a postext 1.4, una fórmula amb \tag no s'arribava a dibuixar.)
El resolver i l'stripper segueixen el mateix patró que les altres seccions:
import {
DEFAULT_MATH_CONFIG,
resolveMathConfig,
stripMathDefaults,
} from 'postext';
const resolved = resolveMathConfig(config.math);
const minimal = stripMathDefaults(config.math);#Arrencar el motor de fórmules
MathJax es carrega a demanda, no amb la resta del motor. Quan compons al fil principal, arrenca'l abans de construir un document amb fórmules:
import { buildDocument, initMathEngine, renderPage } from 'postext';
await initMathEngine(); // carrega MathJax una vegada; les crides següents es resolen a l'instant
const doc = buildDocument({ markdown: 'Euler: $e^{i\\pi}+1=0$.' }, config);
document.body.append(renderPage(doc.pages[0], doc));- Fins que el motor arrenca, les fórmules són provisionals. Cadascuna es compon com una caixa grisa de mida estimada. Si un document amb fórmules es compon sense haver cridat mai
initMathEngine(), la consola mostra un avís, una sola vegada. - El worker de composició l'arrenca per tu. Un build a
postext/workercridainitMathEngine()pel seu compte quan el markdown conté un$. - Compondre ja, i un altre cop quan estigui a punt.
isMathReady()indica si el motor està en marxa.onMathReady(fn)cridafntan bon punt ho està (a l'instant si ja ho estava) i retorna una funció que cancel·la la crida. Un editor pot mostrar les caixes provisionals de seguida i tornar a compondre quan arriba MathJax; mentreinitMathEngine()està en curs no s'imprimeix cap avís. - Errors.
initMathEngine()es rebutja si MathJax no es pot carregar, i una crida posterior ho torna a intentar. - Amb qualsevol bundler, a Node o des d'una CDN. MathJax viatja dins del paquet com un únic mòdul preempaquetat (uns 1,8 MB sense comprimir, que només baixa
initMathEngine). L'import { initMathEngine } from 'https://esm.sh/postext'de sempre funciona; no cal?bundle. El paquetmathjax-fullnomés cal per compilar postext, de manera que instal·lar postext no l'instal·la. MathJax i l'analitzador mhchem que inclou tenen llicència Apache-2.0: els seus avisos i el text de la llicència viatgen al costat del mòdul, adist/math/THIRD_PARTY_LICENSES.txt. - Un motor per pàgina. El motor i la seva memòria cau de fórmules representades són compartits per tot el que importa
postexten el mateix context de JavaScript (consulta Estat global compartit en una pàgina).
Per a la gramàtica del document ($...$, $$...$$, com escapar un dòlar literal), consulta Format del document.
#Notes a peu de pàgina
La propietat footnotes fixa on van les notes citades amb [^id], com es numeren i quin aspecte tenen. El marcatge es descriu a Format del document.
interface FootnotesConfig {
placement?: 'column' | 'chapterEnd'; // Peu de la columna que cita, o després del capítol.
numbering?: 'chapter' | 'document' | 'page' | 'column'; // Reiniciar a cada capítol, continuar, o reiniciar a cada pàgina / columna.
numberFormat?: string; // decimal, lower-roman, circled-decimal (①)…
markerPosition?: 'auto' | 'superscript' | 'inline'; // Volada, o sobre la línia de base.
markerSize?: Dimension; // Mida de la crida en línia; em és el text que l'envolta.
chapterEndAlign?: 'foot' | 'text'; // chapterEnd: notes al peu de la columna, o sota el text.
fontSize?: Dimension; // Cos de la nota; em és el cos del text.
lineHeight?: Dimension; // Interlineat de la nota; em és el cos de la nota.
color?: ColorValue; // Color de la nota; sense valor, el del cos.
textAlign?: TextAlign; // Sense valor, l'alineació del cos.
hangingIndent?: Dimension; // Sagnat de les línies següents d'una nota.
spaceBetween?: Dimension; // Espai entre dues notes.
spaceAbove?: Dimension; // Espai entre el text i el filet; em és el cos del text.
spaceBelowRule?: Dimension; // Espai entre el filet i la primera nota.
separator?: {
enabled?: boolean; // Dibuixar el filet.
width?: number; // Longitud del filet, fracció de l'amplada de columna.
lineWidth?: Dimension; // Gruix del filet.
color?: ColorValue; // Sense valor, el color de les notes.
};
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
placement | 'column' | 'chapterEnd' | 'column' | 'column' compon cada nota al peu de la columna que conté la línia que la cita, sota un filet curt; en una maqueta d'una columna és el peu de la pàgina. 'chapterEnd' compon totes les notes d'un capítol després del seu darrer bloc, en l'ordre en què es citen. |
numbering | 'chapter' | 'document' | 'page' | 'column' | 'chapter' | 'chapter' torna a començar per 1 sota cada títol de nivell 1 i al principi de cada document. 'document' continua per tot el document i, en un llibre maquetat capítol a capítol, d'un capítol al següent (continuationAfter porta l'últim número a continuation.footnoteNumber). 'page' torna a començar per 1 a cada pàgina i 'column' a cada columna, comptant les notes on les col·loca la maquetació (les columnes d'una pàgina en ordre de lectura): el 页下注 habitual d'un llibre xinès. El document es maqueta, es numera segons on han caigut les seves notes i es torna a maquetar fins que els números es mantenen (com a màxim tres vegades més). Tots dos valen per a les notes al peu de la columna: amb placement: 'chapterEnd' les notes es numeren per capítol. |
numberFormat | string | 'decimal' | Com s'escriuen els números, amb qualsevol de les grafies que accepta la configuració de numeració: 'decimal', 'lower-roman', 'lower-alpha', 'circled-decimal' (o '①'), 'cjk-decimal', '一'… El fan servir la crida i el número que obre la nota. circled-decimal escriu en decimal els números més grans que 50. Un nom desconegut numera en decimal, amb un avís unknownNumberFormat. |
markerPosition | 'auto' | 'superscript' | 'inline' | 'auto' | 'superscript' posa volats, a mida reduïda, la crida del text i el número que obre la nota. 'inline' els compon sobre la línia de base: la crida a markerSize, el número de la nota a la mida de la nota; en text vertical, una crida en cercle en línia va dreta en una casella pròpia. 'auto' és en línia amb circled-decimal i volada amb qualsevol altre format. En tots els casos la crida segueix el caràcter que la precedeix i mai no obre una línia. |
markerSize | Dimension | 1em | Mida d'una crida en línia; em és la mida del text que l'envolta (0.75em és una reducció habitual). No afecta una crida volada. |
markerTemplate | string | '' | Com s'escriu el número d'una nota; el representa en el format de numeració i les xifres del document: '()' dona les crides entre parèntesis dels llibres àrabs, «(١)». Escriu igual la crida del text i el número que obre la nota. Una plantilla sense es llegeix com el valor per defecte. |
noteNumberPosition | 'auto' | 'superscript' | 'inline' | 'auto' | On va el número que obre la nota: volat, o sobre la línia a la mida de la nota. 'auto' segueix markerPosition. Els llibres àrabs posen volada la crida del text i sobre la línia el número de la nota. |
chapterEndAlign | 'foot' | 'text' | 'foot' | Amb placement: 'chapterEnd': 'foot' compon les notes que tanquen una columna al peu d'aquesta, amb les línies sobrants entre el text i les notes, igual que les notes al peu de columna. 'text' les compon just sota el text. |
fontSize | Dimension | 0.8em | Cos del text de les notes. em i rem són el cos del text. Les notes fan servir la família i els pesos del cos. |
lineHeight | Dimension | 1.25em | Interlineat de les notes; em és el cos de la nota. Les notes queden fora de la retícula de línies de base: s'apilen des del peu de la columna i el text de sobre continua a la retícula. |
color | ColorValue | color del cos | Color del text de les notes. |
textAlign | TextAlign | alineació del cos | Alineació del text de les notes. |
hangingIndent | Dimension | 0 | Sagnat de la segona línia i següents d'una nota, per alinear-les després del seu número. |
spaceBetween | Dimension | 0 | Espai entre dues notes. |
spaceAbove | Dimension | 0.5em | Espai entre l'última línia de text i el filet; em és el cos del text. Amb 'chapterEnd' i chapterEndAlign: 'text', spaceAbove + spaceBelowRule és l'espai entre el text i la primera nota. |
spaceBelowRule | Dimension | 0.4em | Espai entre el filet i la primera nota. |
separator.enabled | boolean | true | Dibuixar el filet sobre les notes de cada columna. Amb false els espais de sobre es mantenen. |
separator.width | number | 0.3 | Longitud del filet com a fracció de l'amplada de la columna (0–1), des de la seva vora esquerra. |
separator.lineWidth | Dimension | 0.5pt | Gruix del filet. |
separator.color | ColorValue | color de les notes | Color del filet. |
footnotes: {
fontSize: { value: 7.5, unit: 'pt' },
lineHeight: { value: 9.5, unit: 'pt' },
hangingIndent: { value: 0.8, unit: 'em' },
separator: { width: 0.25, lineWidth: { value: 0.4, unit: 'pt' } },
}Com es componen les notes al peu de columna:
- La nota i la seva crida comparteixen columna. Abans de col·locar una línia, la maquetació suma l'alçada de les notes que aquella línia cita per primer cop (i la del filet, en la primera nota de la columna). Una línia les notes de la qual no caben a sota passa a la columna següent amb la resta del seu paràgraf, segons les regles de vídues i òrfenes. L'àrea de text de la columna s'escurça en l'alçada de les notes, de manera que l'equilibrat de columnes i la banda de tancament d'un capítol només compten el text.
- Diverses notes en una columna s'apilen en l'ordre de cita sota un únic filet. Una nota citada de nou més endavant conserva el seu número i no es torna a compondre.
- Flotants al peu. Una figura que ocupa el peu d'una columna després que les seves notes s'hagin compost queda damunt d'elles; les notes compostes després de la figura queden damunt d'ella.
- Requadres. Una nota citada dins d'un requadre (en línia, flotant o fix) va al peu de la columna on continua el text després del requadre, normalment la mateixa. Un requadre que tanca el document deixa les seves notes al peu de la columna on ha acabat el text.
- Límits. Una nota mai no es parteix: una de més alta que la columna la desborda. Les crides en peus de figura, cel·les de taula i títols no es llegeixen (s'imprimeixen tal qual).
- Sortida. El canvas, l'HTML i el PDF pinten les notes, les crides (un número volat) i el filet. Al PDF cada crida enllaça amb la seva nota, i un PDF etiquetat marca cada nota com a element
Noteamb un/IDúnic llistat a l'/IDTreede l'arbre d'estructura (PDF/UA-1). Les notes sónVDTBlockambfootnoteNote, apage.floats; els filets són apage.footnoteAreas. - Avisos.
undefinedFootnote(una crida sense definició: el número s'imprimeix sobre una nota buida) iunusedFootnote(una definició que cap crida no cita: no es compon).
El resolutor i el depurador segueixen el patró de les altres seccions:
import { DEFAULT_FOOTNOTES_CONFIG, resolveFootnotesConfig, stripFootnotesDefaults } from 'postext';#Referències creuades
La propietat crossRefs fixa les paraules que una referència creuada imprimeix al voltant d'un número o d'una pàgina, i l'estil que pren un :ref que no n'indica cap. Cada plantilla porta {n} on va el número; una que no el porta rep el número després d'un espai no separable ("§" imprimeix § 3.2). Una plantilla sense definir segueix la llengua del document: capítulo / sección / pág. en castellà, chapter / section / p. en anglès, 第{n}章 / 第{n}节 / 第{n}页 en xinès, i el mateix per al francès, l'alemany, l'italià, el portuguès, el català i el neerlandès.
interface CrossRefsConfig {
chapter?: string; // Words around a level-1 heading's number: "chapter {n}".
section?: string; // Around any other heading's number: "section {n}".
page?: string; // Around a page number: "p. {n}".
defaultStyle?: 'default' | 'number' | 'title' | 'page'; // A :ref without style=.
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
chapter | string | segons la llengua | Una referència a un títol de nivell 1: "capítulo {n}". Un número la plantilla del qual ja escriu la paraula (Capítulo {1}, 第{1:一}章) s'imprimeix tal qual. |
section | string | segons la llengua | Una referència a un títol de nivell 2 a 6: "sección {n}", "§ {n}". |
page | string | segons la llengua | Una referència de pàgina (style=page): "pág. {n}", "página {n}". |
defaultStyle | 'default' | 'number' | 'title' | 'page' | 'default' | El que imprimeix un :ref a un títol o a una àncora sense style=. 'default': un títol numerat amb la seva paraula i el seu número, un sense número amb el seu títol, una àncora amb el seu text. Una referència que indica style el conserva, i les referències a figures i taules no canvien. |
Les referències prenen el color, el pes i la inclinació de totes les referències (bodyText.referenceColor, referenceBold, referenceItalic).
#Cites
La propietat citations tria l'estil de cita i com es veuen les cites i la bibliografia. El marcatge es descriu a Cites i bibliografia; l'estil l'aplica el paquet postext-citeproc.
interface CitationsConfig {
style?: string; // 'apa', 'ieee', 'chicago-notes-bibliography'… or 'custom'
customStyle?: string; // a whole CSL style (.csl XML), used with style: 'custom'
locale?: string; // CSL locale; the document language when unset
link?: boolean; // citations link to their entries
marker?: 'style' | 'brackets' | 'parentheses' | 'superscript' | 'corner';
collapseRanges?: boolean;
notes?: 'footnote' | 'warichu';
bibliography?: {
title?: string; // unset: the document language's word; '' or ' ': none
scope?: 'book' | 'chapter';
auto?: boolean;
fontSize?: Dimension;
lineHeight?: Dimension;
hangingIndent?: Dimension;
entrySpacing?: Dimension;
labelWidth?: Dimension;
labelAlign?: 'left' | 'right';
doi?: 'link' | 'text' | 'hide';
includeUncited?: boolean;
groupByLanguage?: boolean;
};
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
style | string | 'apa' | L'id d'un estil inclòs (vegeu Estils) o 'custom'. L'estil decideix el que diuen les cites i les entrades: noms, dates, ordre, puntuació i si les cites són notes. |
customStyle | string | — | Un estil CSL complet, l'XML d'un fitxer .csl, que es fa servir quan style és 'custom'. El Sandbox el carrega des d'un fitxer. |
locale | string | llengua del document | La configuració regional CSL en què l'estil escriu les seves paraules (es-ES, en-US, zh-CN…). |
link | boolean | true | Una cita enllaça amb la seva entrada a la bibliografia (enllaç al PDF, àncora a l'HTML, clic al Sandbox). |
marker | 'style' | 'brackets' | 'parentheses' | 'superscript' | 'corner' | 'style' | Com marca la cita un estil numèric: com l'escriu l'estil, [1], (1), en superíndex o 〔1〕 (dret en text vertical). El localitzador va després del número. |
collapseRanges | boolean | true | Números seguits com a interval: 1–3 amb una marca pròpia, [2]–[4] amb la d'IEEE. false els deixa separats. |
notes | 'footnote' | 'warichu' | 'footnote' | On posa les seves cites un estil de notes: notes a peu de pàgina (com diu footnotes) o notes de dues línies dins del rengle (夹注). |
bibliography.title | string | segons la llengua | Títol sobre la llista, un paràgraf en negreta. En blanc: cap. Un títol propi va sobre :::bibliography. |
bibliography.scope | 'book' | 'chapter' | 'book' | Una llista amb totes les obres que cita el llibre, o una per capítol amb les que cita cadascun. En un document de diversos capítols cada H1 comença una llista nova; amb auto, un capítol que no col·loca el seu :::bibliography rep la llista al final. |
bibliography.auto | boolean | true | Posa la llista després del text (després de l'últim capítol, si abasta tot el llibre) quan cap :::bibliography no la situa. |
bibliography.fontSize | Dimension | 0.9em | Mida de les entrades; em és la mida del text. |
bibliography.lineHeight | Dimension | interlineat del text | Interlineat de les entrades. |
bibliography.hangingIndent | Dimension | 2em | Sagnat de les línies següents d'una entrada sense número. |
bibliography.entrySpacing | Dimension | 0.3em | Espai entre dues entrades. |
bibliography.labelWidth | Dimension | rètol més llarg | Amplada de la columna dels números d'una llista numerada: el text de cada entrada comença a aquesta distància, tant en la primera línia com en les següents, de manera que 9. i 10. comparteixen la columna. El número continua formant part del text de l'entrada. |
bibliography.labelAlign | 'left' | 'right' | 'left' | On es col·loca el número a la seva columna: contra la vora esquerra o contra el text (9. i 10. acaben alhora). |
bibliography.doi | 'link' | 'text' | 'hide' | 'link' | DOI i URL com a enllaços, com a text o fora. |
bibliography.includeUncited | boolean | false | Llista totes les referències, citades o no (com nocite: "@*"). |
bibliography.groupByLanguage | boolean | false | Primer les obres en xinès, japonès i coreà; després les altres. Només en estils autor-data i autor-pàgina: una llista numerada conserva l'ordre dels seus números. |
#Tipografia de l'Àsia oriental
La propietat cjk fixa com es compon el text en xinès, japonès i coreà: quines convencions regionals segueix, on se'n poden tallar les línies, quina amplada n'ocupa la puntuació i si penja, l'espai entre xinès i llatí, la retícula de caràcters de la caixa de text, i com s'imprimeixen les marques xineses, les lectures ruby i les notes warichu del text. Tots els camps són opcionals, i 'auto' segueix la regió de la llengua del document (locale), de manera que un llibre amb locale: 'zh-Hant' talla les seves línies i compon la seva puntuació a la manera de Taiwan sense fixar res més. Composició xinesa explica les regles que hi ha darrere d'aquesta configuració, regió per regió, amb dues configuracions completes.
interface CjkConfig {
region?: 'auto' | 'mainland' | 'taiwan' | 'hongkong'; // Convencions regionals.
lineBreak?: 'auto' | 'none' | 'basic' | 'gb' | 'strict'; // Quins signes no poden obrir ni tancar una línia.
punctuationWidth?: 'auto' | 'fullwidth' | 'kaiming' | 'lineEndHalf' | 'halfwidth';
compressAdjacent?: 'auto' | boolean; // Dos signes seguits ocupen 1,5 quadratins.
trimLineStart?: 'auto' | boolean; // Els parèntesis a la vora d'una línia perden la meitat exterior.
hangingPunctuation?: 'none' | 'allow' | 'force';
latinSpacing?: Dimension; // Entre xinès i llatí; per defecte 0,25 em.
uprightDigits?: 0 | 2 | 3 | 4; // Text vertical: nombres en una casella; per defecte 2.
grid?: { enabled?: boolean; charsPerLine?: number; linesPerPage?: number; show?: boolean };
emphasis?: 'auto' | 'italic' | 'dots'; // Què fa *…* amb els caràcters xinesos.
bookTitleMark?: 'auto' | 'brackets' | 'wavy' | 'none'; // Què imprimeix :book[…].
annotationColor?: ColorValue; // Punts i línies de nom i títol; per defecte, el color del text.
ruby?: { fontFamily?: string; fontSize?: Dimension; color?: ColorValue; position?: 'auto' | 'over' | 'under' | 'right' };
warichu?: { fontSize?: Dimension; color?: ColorValue; open?: string; close?: string };
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
region | 'auto' | 'mainland' | 'taiwan' | 'hongkong' | 'auto' | Les convencions que segueix el text, segons les regions que descriuen els Requisits de composició del text xinès del W3C (clreq). 'auto' pren la regió de locale, o de bodyText.hyphenation.locale si locale no està definit: zh, zh-Hans, zh-CN i zh-SG donen 'mainland' (la Xina continental); zh-Hant i zh-TW donen 'taiwan'; zh-HK i zh-MO donen 'hongkong'; qualsevol altra llengua dona 'mainland'. La regió tria els valors per defecte de lineBreak, punctuationWidth, compressAdjacent i trimLineStart. |
lineBreak | 'auto' | 'none' | 'basic' | 'gb' | 'strict' | 'auto' | Quins signes no poden obrir ni tancar una línia (clreq §6.1.1; vegeu la taula següent). 'auto': 'gb' a la Xina continental, 'basic' a Taiwan i Hong Kong. |
punctuationWidth | 'auto' | 'fullwidth' | 'kaiming' | 'lineEndHalf' | 'halfwidth' | 'auto' | Quina amplada ocupen els signes d'amplada completa (vegeu Amplades de la puntuació). 'auto': 'kaiming' a la Xina continental, 'fullwidth' a Taiwan i Hong Kong. |
compressAdjacent | 'auto' | boolean | 'auto' | Dos signes seguits (。」, 》(, :“) cedeixen el mig quadratí de blanc que queda entre ells, i el parell ocupa 1,5 quadratins en lloc de 2. 'auto': activat a la Xina continental i Hong Kong, desactivat a Taiwan. |
trimLineStart | 'auto' | boolean | 'auto' | Un parèntesi o unes cometes d'obertura que comencen una línia cedeixen el mig quadratí que porten al davant, i els de tancament que l'acaben, el del darrere. 'auto': activat a la Xina continental i Hong Kong, desactivat a Taiwan. |
hangingPunctuation | 'none' | 'allow' | 'force' | 'none' | Si un signe de pausa o de final de frase pot penjar fora del final de la línia (vegeu Puntuació penjada). |
latinSpacing | Dimension | { value: 0.25, unit: 'em' } | L'espai entre un caràcter xinès i la lletra llatina o la xifra que té al costat, en quadratins del cos del text CJK o en qualsevol mesura; 0 el treu (vegeu Espai entre xinès i llatí). |
uprightDigits | 0 | 2 | 3 | 4 | 2 | Text vertical: un nombre de fins a tantes xifres queda dret en una sola casella (tate-chu-yoko), tret de dins d'una frase llatina, les paraules de la qual segueix llavors ajagut; 0 ho desactiva (vegeu Nombres en text vertical). |
grid | { enabled?, charsPerLine?, linesPerPage?, show? } | desactivada | La caixa de text en caràcters per línia i línies per pàgina (vegeu Retícula de caràcters). |
emphasis | 'auto' | 'italic' | 'dots' | 'auto' | Què fa l'èmfasi de Markdown (…) amb els caràcters xinesos: 'dots' posa un punt d'èmfasi sota cadascun (a la seva dreta en text vertical), com :dots[…], i les lletres llatines del mateix èmfasi conserven la cursiva; 'italic' els inclina, cosa que una font xinesa només pot simular. 'auto': 'dots' quan la llengua del document és el xinès, 'italic' en els altres casos (vegeu Marques, ruby i warichu). |
bookTitleMark | 'auto' | 'brackets' | 'wavy' | 'none' | 'auto' | Què imprimeix :book[…]: 《》 al voltant del títol (〈〉 dins d'un altre títol), la línia ondulada de títol a sota, o el títol sol. 'auto': signes a la Xina continental, línia ondulada a Taiwan i Hong Kong. |
annotationColor | ColorValue | el color del text | Color dels punts d'èmfasi i de les línies de nom propi i de títol; el que prenen per defecte les lectures ruby i les notes warichu. |
ruby | { fontFamily?, fontSize?, color?, position? } | mig cos, 'auto' | Les lectures de :ruby[…] i {紅樓|hóng|lóu}: font (per defecte, la del text), mida (per defecte { value: 0.5, unit: 'em' } del text; el zhuyin al 60 % d'aquesta), color, i on van quan un ruby no ho diu ('auto': el zhuyin a la dreta de cada caràcter, el pinyin damunt del text en horitzontal i a la seva dreta en vertical). |
warichu | { fontSize?, color?, open?, close? } | mig cos, sense signes | Les notes en dues files de :warichu[…]: la mida de la nota (per defecte mig em, de manera que les dues files omplen l'em de la línia), el seu color i els signes que es componen a la mida del text abans de la primera fila i després de l'última (manen els open / close de cada nota). |
| Nivell | Mai al començament d'una línia | Mai al final d'una línia |
|---|---|---|
none | Res: una línia es pot tallar entre dos caràcters qualssevol, com fa la premsa de Taiwan i Hong Kong. | Res. |
basic | Els signes de pausa i de final de frase 、,;:。!?.‼⁇⁈⁉; les cometes de tancament ” ’ 」 』 i els parèntesis i claudàtors de tancament )〕]}】〗》〉; els connectors – ~ ~ i un guió llarg — sol entre dues paraules; els punts volats · ‧ ・; les marques d'iteració 々〻ゝゞヽヾ i ー; les unitats d'un nombre % ‰ ° ℃ % i els quadres d'unitat ㎡ ㎏ ㏄. | Les cometes d'obertura “ ‘ 「 『 i els parèntesis i claudàtors d'obertura (〔[{【〖《〈; els signes de moneda ¥ $ € £. |
gb | basic, i la barra / / (GB/T 15834—2011 §5.1.9). | basic, i la barra. |
strict | gb, i el guió llarg doble —— ⸺ i els punts suspensius …… ⋯⋯. | gb. |
En tots els nivells:
- —— i …… formen una unitat de dos quadratins que mai no se separa; dues unitats així seguides es poden separar entre si.
- Un nombre conserva els seus signes i la seva unitat (¥5,999, 50%, 50%, 120㎡), també amb un espai entre ells (−3 ℃, 50 %), i una paraula llatina es manté sencera: el text occidental entre caràcters CJK es compon com un tram que cap línia no talla per dins, tret que el tram sol sigui més ample que la línia (llavors es divideix per una síl·laba amb guionet, o per l'últim caràcter que hi cap; una adreça web, per les seves unions). Els quadres d'unitat ㎡ ㎏ ㎞ ㏄ (U+3371–337A, U+3380–33DF, U+33FF) van en el tram del seu nombre i no fan CJK un paràgraf llatí.
- Tampoc no es talla un nombre o una paraula en xifres i lletres d'amplada completa (123456, 50%, ¥599, 3.14, 12:30, ABC); una línia justificada reparteix l'espai entre els seus caràcters igual que entre els han.
- Un espai entre paraules és un tall només on les regles ho permeten: la línia no s'hi talla quan el caràcter següent no pot obrir una línia (
参见图表 ( 第三章 )mai no obre una línia amb )) o l'últim abans de l'espai no la pot tancar. - Una crida de nota, un
:refi un superíndex o subíndex van amb el caràcter que els precedeix. - L'espai ideogràfic U+3000 és un caràcter d'un quadratí: una línia es pot tallar després d'ell, mai abans; mai no s'estira ni desapareix al començament d'una línia. Per al sagnat de dos caràcters dels paràgrafs xinesos fixa
bodyText.firstLineIndent: { value: 2, unit: 'em' }en lloc d'escriure dos U+3000 (l'analitzador descarta els que obren un paràgraf).
Quan un caràcter no cap a la línia i pot obrir la següent, hi baixa i una línia justificada reparteix el que sobra. Quan no pot obrir la línia següent (una coma, un parèntesi de tancament, el caràcter que segueix un d'obertura), la línia intenta primer admetre'l comprimint-se (l'anomenat push-in, clreq §6.2.2.3): si el blanc que pot cedir —vegeu Amplades de la puntuació— cobreix el que sobresurt, el caràcter (amb els signes que hi han d'anar, unes cometes de tancament després d'un punt) entra a la línia i la línia s'ajusta a la mesura. Si no, la línia li cedeix caràcters (l'anomenat push-out): el tall retrocedeix fins a l'últim punt que les regles permeten, i una línia justificada reparteix el que sobra. En una línia més estreta que un nombre amb el seu punt final res no pot tancar la línia, i es talla abans del punt de totes maneres.
A quins paràgrafs s'aplica: als que tenen més caràcters CJK (han, kana, bopomofo, hangul) que espais entre paraules, encara que no n'hi hagi dos de seguits (第1条、第2条, 价¥5,999。好). Un espai al costat d'un caràcter o un signe CJK no és un espai entre paraules: 2026 年 9 月 28 日 es compon com 2026年9月28日. Es componen línia a línia, igual en la via sense format que en la de format, així que un paràgraf amb una paraula en negreta es talla exactament com el mateix text sense ella. Un paràgraf llatí que cita un títol o un nom xinès té més espais que caràcters: conserva la divisió òptima de línies, i una línia es pot tallar al costat dels seus caràcters CJK amb les mateixes regles. Els peus de figura, les cel·les de taula, les notes i els requadres també es tallen al nivell del document. La partició per diccionari no arriba a les paraules llatines d'un paràgraf CJK.
Una línia CJK justificada que no és l'última del seu paràgraf s'eixampla fins a la mesura en aquest ordre (clreq §6.2.2.4): els espais entre paraules occidentals, fins a mig quadratí cadascun; els espais entre xinès i llatí, fins a mig quadratí cadascun; després tots els buits entre caràcters, i aquests espais, per igual. No s'afegeix espai dins d'una paraula llatina, d'un nombre o d'un signe de dos quadratins, ni al costat d'un connector o una barra. L'espaiat es fixa per segment de la línia (VDTLineSegment.tracking, px després de cada caràcter, inclosos en el width del segment), que el canvas, l'HTML i el PDF pinten com a espaiat entre lletres; una paraula llatina seguida d'un buit dona a la seva última lletra un segment propi. Un segment conté un tram occidental, o caràcters d'un mateix estil, enllaç i espaiat que avancen el mateix, així que el Sandbox en reparteix l'amplada per igual entre ells en col·locar el cursor, i un enllaç cobreix només els seus propis caràcters. Quan una línia necessitaria més de mig quadratí entre els seus caràcters (o més que bodyText.maxJustifyTracking, si està definit), es compon amb aquest màxim i queda curta: la línia porta cjkLoose i ragged, i la composició dona un avís de contingut cjkLooseLine amb el text de la línia. Sol passar per una paraula llatina llarga o per una adreça web que no hi pot pujar. Una línia sense cap caràcter CJK (el començament d'una adreça web llarga) queda en bandera sense l'avís, com una línia llatina d'una sola paraula. Vegeu Partició de mots i justificació.
#Amplades de la puntuació
Un signe xinès d'amplada completa és mig quadratí de dibuix i mig de blanc, i els ajustos només canvien el blanc, mai el dibuix (clreq §6.3.2). El blanc va davant del dibuix en un parèntesi o unes cometes d'obertura, darrere en els de tancament, i darrere en els signes de pausa i de final de frase de la Xina continental 、,。.;:?!, que van a la cantonada de la seva caixa; els signes que Taiwan i Hong Kong centren (、,。.;:) i els punts volats guarden un quart de quadratí a cada costat. ?! ocupen sempre un quadratí en text horitzontal de Taiwan i Hong Kong, i :;?! en text vertical a tot arreu. El punt volat de la Xina continental ocupa mig quadratí amb qualsevol estil, centrat, en els dos sentits d'escriptura (GB/T 15834, clreq §5.1): un dibuix d'amplada completa cedeix el blanc dels dos costats, i en text vertical la seva cel·la ja fa mig quadratí.
| Estil | Dins de la línia | Al final de la línia |
|---|---|---|
fullwidth (全角式) | Tots els signes, un quadratí. | Un quadratí; un parèntesi de tancament, mig, amb trimLineStart. |
kaiming (开明式) | 。.?! un quadratí; ,、;:, parèntesis, cometes i punts volats, mig. La majoria dels llibres de la Xina continental. | Tots els signes, mig quadratí. |
lineEndHalf (行末半角) | Tots els signes, un quadratí. | Tots els signes, mig quadratí (la GB/T 15834—2011 §5.1.10 al peu de la lletra). |
halfwidth (半角式) | Tots els signes, mig quadratí, com als diccionaris. | Mig quadratí. |
Amb compressAdjacent, dos signes seguits cedeixen el blanc que queda entre ells (les vuit regles dels esborranys anteriors de clreq): un parèntesi de tancament després d'un altre de tancament o després d'un signe de pausa o de final de frase de la Xina continental (。」, no després dels signes centrats de Taiwan i Hong Kong), un signe de pausa o de final de frase després d'un parèntesi de tancament (」,), un parèntesi d'obertura després de qualsevol d'aquests o després d'un altre d'obertura (,「, 》(, 「『), i un quart de quadratí entre un punt volat i un parèntesi de tancament anterior o un d'obertura posterior. Mai més del que deixa el parell en 1,5 quadratins: amb Kaiming 。” ja ocupa 1,5 i així es queda, i 》( n'ocupa un. Amb compressAdjacent o sense, Kaiming passa el blanc d'un signe de final de frase darrere del signe de tancament que el segueix: els dos traços queden junts i el mig quadratí va després de la cometa (。”␣母, no 。␣”母), i al final d'una línia desapareix, com el d'un punt sol. Amb trimLineStart, un parèntesi d'obertura que comença una línia cedeix el blanc que porta al davant, i el seu traç queda alineat amb la vora del text (a la primera línia d'un paràgraf queda mig quadratí dins del sagnat), i un de tancament que acaba una línia cedeix el del darrere. Un signe centrat cedeix un quart de quadratí per cada costat, mai mig per un. Quan una línia es talla entre dos signes, cap no conserva la compressió: cadascun es compon com a signe a la vora d'una línia (una , d'amplada completa el 「 de la qual obre la línia següent acaba la seva amb un quadratí sencer).
Una línia admet un caràcter que no pot obrir la línia següent (push-in) quan el blanc que encara pot cedir cobreix el que sobresurt, en l'ordre de clreq: els espais entre paraules fins a un quart de quadratí, després els punts volats, els parèntesis, els signes de pausa, els espais entre xinès i llatí fins a un vuitè de quadratí i, al final, els signes de final de frase, cada pas repartit per igual. kaiming només deixa baixar a mig quadratí els seus signes de final de frase, i només en aquest cas: a una línia a la qual simplement li falta lloc per a un caràcter més se li reparteix l'espai, així que 。?! conserven el seu quadratí dins de la línia. lineEndHalf deixa baixar a mig quadratí tots els signes; els de fullwidth no cedeixen res (un llibre d'amplada completa conserva la seva retícula) i als de halfwidth no els queda res per cedir.
Els signes que el llatí comparteix amb el xinès (“ ” ‘ ’ … — ·) ocupen en text xinès la caixa d'un signe xinès, sigui quin sigui l'avanç que els dona la font: LXGW WenKai dibuixa “ ” amb 0,35 em, Noto Serif SC el guió llarg amb 0,89 em i el · amb un terç d'em. Compten com a xinesos quan el caràcter més proper a un costat o a l'altre (saltant altres signes d'aquests) és xinès, o quan no tenen text occidental al costat. La cometa d'obertura es dibuixa al final de la seva caixa d'un em i la de tancament al principi; el punt volat, els punts suspensius i un guió llarg sol, al centre; una parella de punts suspensius (……) es compon com la font compon els dos junts i es centra en els seus dos em. El 破折号 (——) és un sol filet continu: cada guió s'estira sobre el seu em des del marge lateral de la mateixa font, els dos traços se superposen a la unió i pugen al centre dels caràcters (VDTLineSegment.inkScale, una escala que només afecta la pintura). Després l'estil ajusta la caixa com la de qualsevol altre signe. Amb text occidental a banda i banda (他说:He said “yes” and left.) conserven l'avanç de la font, i un dibuix que ja fa un em no canvia res.
Els renderitzadors no deixen que el navegador ajusti la puntuació pel seu compte. Chrome estreny a mig em el primer de dos signes seguits quan els mesura o els pinta en un sol traç (本)》录 en Noto Serif SC ocupa així 3,5 em), de manera que dos signes seguits es mesuren i es pinten per separat, i les línies HTML amb text CJK porten text-spacing-trim: space-all i text-autospace: no-autospace, amb les funcions chws, halt i vchw desactivades.
Al VDT un signe que ha cedit blanc és un segment propi, el width del qual és l'avanç que conserva, amb inkOffset (px): els renderitzadors pinten el seu dibuix a x + inkOffset, de manera que un signe que ha cedit el blanc del davant (un parèntesi d'obertura al començament d'una línia) es dibuixa aquesta distància abans de la seva caixa (un desplaçament negatiu). Un signe compartit compost en una caixa xinesa porta el lloc del seu dibuix dins de la caixa, menys el blanc que hagi cedit al davant, i pot ser positiu. El PDF mostra aquest dibuix amb un espaiat entre caràcters que acaba el seu avanç on acaba la seva caixa, perquè un lector no el vegi mai muntat sobre el caràcter següent, i posa la seva línia en un /Span amb /ActualText (vegeu Espai entre xinès i llatí). Quin costat del signe porta el blanc ho decideix la regió, no la font: compon un llibre amb una font de la seva regió (Noto Serif SC per a la Xina continental, TC per a Taiwan, HK per a Hong Kong); una font tradicional amb una etiqueta de la Xina continental comprimeix el costat equivocat dels seus signes centrats.
#Puntuació penjada
hangingPunctuation: 'allow' deixa que un de 、,。. (a la Xina continental, on els signes van al principi de la seva caixa, també ;:?!) pengi fora del final de la línia quan altrament obriria la següent i no n'hi ha prou a comprimir la línia per fer-l'hi cabre; mai en text horitzontal de Taiwan i Hong Kong, on els signes centrats semblarien tallats (en vertical sí que pot). En text vertical el signe penjat queda sota el peu de la línia. 'force' fa penjar aquest signe sempre que acaba una línia (tret de l'última d'un paràgraf), i tan bon punt no hi cap. Un signe mai no penja si el toca un altre signe (。」, ,「). El segment penjat porta hangs; el bbox.width de la línia, la seva justificació i la seva alineació el deixen fora, i el canvas i el PDF eixamplen el retall de la columna amb el signe penjat més ample (hangingPunctuationOverhang), així que mai no es talla. La majoria dels llibres xinesos no pengen la puntuació; clreq només la recomana juntament amb una retícula de caràcters.
#Espai entre xinès i llatí
latinSpacing (per defecte, un quart de quadratí) posa un espai entre un caràcter xinès (o kana) i la lletra llatina o la xifra europea que té al costat: 用iPhone拍照 i 1999年 es componen 用 iPhone 拍照 i 1999 年. No es posa al començament ni al final d'una línia, ni entre un caràcter llatí i un signe xinès (用iPhone, no porta res abans de la coma), ni dins de parèntesis xinesos ((iPhone)), ni al costat d'un signe que no és lletra ni xifra (为¥5,999). Un espai que l'autor ha escrit en aquest punt (用 iPhone 拍照, com el porten molts textos del web) se substitueix per l'espai entre xinès i llatí, no s'hi suma, així que les dues grafies es componen igual; un espai de no separació o un espai ideogràfic es queden com estan. En una línia justificada creix fins a mig quadratí abans de repartir espai entre els caràcters; en una línia que admet un caràcter que no pot obrir la línia següent es redueix fins a un vuitè de quadratí. Una mesura en una unitat que no sigui em es converteix amb els ppp de la pàgina. 0 el treu, i un espai escrit allà continua sent un espai entre paraules.
L'espai és un segment de kind: 'space' amb autospace, text buit (o l'espai que ha escrit l'autor); el seu width és definitiu, i la justificació d'espais dels renderitzadors el deixa com està. Mai no és al text pla, així que la cerca, el copiar i enganxar i els rangs d'origen llegeixen el text tal com es va escriure. El PDF posa cada línia composta en peces (caràcters espaiats, espais entre xinès i llatí, signes que han cedit blanc o que pengen) en un /Span el /ActualText del qual és el text de la línia, de manera que l'extracció de text llegeix 用iPhone拍照 i no 用 iPhone 拍 照, i llegeix com una sola línia una que porta signes de mig quadratí.
#Nombres en text vertical
En text vertical una paraula llatina o un nombre llarg s'ajeuen, i un nombre curt queda dret, amb les xifres una al costat de l'altra en una casella d'un quadratí: el tate-chu-yoko (縱中橫, clreq §2.1.3, text-combine-upright de CSS). uprightDigits fixa quantes xifres pot tenir aquest nombre: 2 (per defecte), 3, 4, o 0 per a cap. 2026年9月28日 amb 2 és el 2026 ajagut, el 9 dret i el 28 dret en una casella.
- El nombre sencer o res: amb
2un nombre de tres xifres va ajagut, mai partit. - Un nombre enganxat a una lletra llatina (
A4,mp3,3D) es queda a la seva paraula, ajagut; igual un nombre amb decimals o separador de milers (3.14,10,000). - Un nombre dins d'una frase llatina segueix la frase: si té una paraula llatina a cada costat, va ajagut amb les paraules (
printed in 49 and 32 copies,chapters 49, 32 and (7) of). A cada costat es mira més enllà dels espais, d'altres nombres, de les crides de nota, de la puntuació d'un tram ajagut (, . : ; ( ) ' " - /), del guió llarg i el guió mitjà i de les cometes angleses (pages 3–5 of,the “49” copies) fins a la primera lletra o el primer caràcter xinès. Un nombre els signes del qual porten a text xinès queda dret (上午12:30:45开会,比分为3:2:1,见图(3)所示,他住在"12"号楼), i també un al costat d'un caràcter o un signe xinès, amb espai o sense (第 3 回,用iPhone 15拍攝,第3 copies), un al costat d'un signe que va dret (a 30×40 print) i un al començament o al final d'un paràgraf, que només té paraula per un costat (49 copies were printed,on page 7.: escriu:sideways[…]per ajeure'l). El paràgraf es llegeix sencer, per sobre dels salts de línia, els èmfasis i els enllaços. - Al costat d'un signe que Unicode posa dret, cada nombre ocupa la seva pròpia casella:
30×40són 30, × i 40, tots drets. - Una casella amb més caràcters dels que caben en un quadratí s'estreny fins al quadratí; l'espaiat i la justificació mai no hi entren, només van darrere.
- Per tallar i justificar, la casella compta com un caràcter xinès, i no porta espai entre xinès i llatí a cap costat.
- El mesurador i tots els motors de pintura veuen les mateixes caselles: el canvas pinta les xifres redreçades i estretes, el PDF igual amb la font horitzontal, i l'HTML les embolcalla en
text-combine-upright: all.
A mà, tres marques en línia manen sobre l'ajust en text vertical i no canvien res en text horitzontal (vegeu Format del document › Orientació en text vertical):
第:tcy[120]回、:upright[GDP]與:sideways[12]:tcy[…] compon el seu text en una casella dreta, :upright[…] posa dret cada caràcter en la seva pròpia casella (les lletres llatines, centrades a la casella) i :sideways[…] ajeu el tram sencer, també els caràcters xinesos. Al VDT, un tram :tcy és un segment amb tcy: true d'un quadratí d'amplada; un :upright o :sideways porta orientation. Els nombres que uprightDigits compon en una casella no porten marca: els troba verticalRuns(graphemes, region, uprightDigits). En un paràgraf, un nombre curt que va amb paraules llatines porta orientation: 'sideways', com si s'hagués escrit :sideways[…]: el seu segment no sempre conté les paraules amb què va.
#Retícula de caràcters
Una caixa de text xinesa s'especifica en caràcters (clreq §7.1.1): el cos × caràcters per línia × línies per pàgina, més l'interlineat i, a dues columnes, l'espai entre columnes. grid la fixa així:
cjk: { grid: { enabled: true, charsPerLine: 28, linesPerPage: 28, show: true } }Amb enabled, la configuració es reescriu abans que res no la llegeixi: cada columna fa charsPerLine quadratins de bodyText.fontSize, i la caixa de text linesPerPage línies de bodyText.lineHeight; amb layoutType: 'double' l'espai entre columnes és layout.gutterWidth arrodonit a un nombre enter de quadratins, com a mínim un. Els marges de page.margins funcionen com a mínims: la caixa de text es col·loca al centre de l'àrea que deixen, i cada marge creix la meitat del lloc sobrant en el seu eix (un llibre amb marges emmirallats conserva la diferència entre l'interior i l'exterior, i tots dos creixen el mateix). Sense valor, charsPerLine i linesPerPage prenen els que hi càpiguen. Un nombre més gran del que hi cap es redueix al que hi cap i s'avisa com a cjkGridClamped als avisos de configuració. El cos i l'interlineat es queden com estan escrits. En una disposició oneAndHalf, charsPerLine és el de la columna principal; la lateral pren el nombre enter de quadratins més proper a l'amplada que li dona sideColumnPercent dins dels marges configurats (com a mínim un), l'espai entre columnes s'arrodoneix com en dues columnes, i sideColumnPercent es reescriu perquè les dues columnes facin quadratins enters. En text vertical (layout.writingMode: 'vertical-rl') els caràcters d'una línia baixen per la pàgina i les línies la travessen, així que charsPerLine mesura l'alçada de la pàgina, una disposició double dona dos pisos, l'un damunt de l'altre, i les caselles de la retícula dibuixada es centren en l'eix sobre el qual es centren els caràcters.
Dues caixes de la pràctica xinesa, en 五号 (10,5 pt) amb 6 pt d'interlineat (lineHeight: 16.5pt):
- 大32开, 140 × 203 mm, 28 × 28: una mesura de 294 pt (103,7 mm) i una caixa de 462 pt (163 mm); uns
page.marginsde 16/20 mm interior i exterior i 18/20 mm superior i inferior li deixen lloc. - 16开, 184 × 260 mm, dues columnes de 23 caràcters en 小五 (9 pt, línies de 13,5 pt) amb un espai entre columnes de dos caràcters: 2 × 207 pt + 18 pt.
show dibuixa la retícula (稿纸) sobre la caixa de text, un quadrat gris clar per posició de caràcter a cada línia (la caixa de quadratí del caràcter al voltant de la seva línia de base), a cada columna de la pàgina (la lateral d'una disposició oneAndHalf, al costat que li toca segons la paritat de la pàgina), al canvas i a l'HTML. El PDF només la dibuixa quan renderToPdf rep characterGrid: true: és una ajuda de pantalla. cjkGridGeometry(config) retorna la retícula que fixa una configuració (els nombres en ús, els marges, la caixa de text) i applyCjkGrid(config) la configuració reescrita.
#Marques, ruby i warichu
El marcatge de marques xineses, ruby i warichu conserva el seu text al paràgraf: la cerca, l'índex general, les àncores de l'índex analític i el text copiat llegeixen els caràcters tal com es van escriure, i d'aquesta configuració només depèn el que es dibuixa al voltant.
- Punts d'èmfasi (着重号,
:dots[…], i*…*sobre caràcters xinesos ambemphasis: 'dots'): un punt per caràcter, centrat en ell (sense comptar l'espaiat d'una línia justificada), mai a la puntuació ni als espais; sota el caràcter en text horitzontal i a la seva dreta en vertical (clreq §5.3.1).styletria punt ple, cercle o punt de sèsam,fill="open"dibuixa el contorn ipos="over|under"el costat. - Línies de nom propi i de títol (专名号
:name[…], 书名号:book[…]ambbookTitleMark: 'wavy'): corren sota la caixa dels caràcters (a la seva esquerra en vertical) i travessen els espais de la seqüència. On es toquen dues seqüències, cada extrem cedeix un vuitè d'em, de manera que:name[賈寶玉]:name[林黛玉]es llegeix com a dos noms. Si uns punts i una línia marquen el mateix text pel mateix costat, la línia va més a prop del text. Amb'brackets', els 《》 són text amb el qual es tallen les línies, es pinten i es copien com qualsevol caràcter; no ocupen cap caràcter del text pla ni del mapa de font (els segments porteninserted). - Ruby: les lectures ocupen l'espai entre línies al costat de la caixa de la base, centrades en ella; una lectura amb lletres llatines (pinyin) sobre la seva base puja el que baixen els traços descendents de la seva font (g, j, p, q, y) i 0,04 em del text més, perquè no toquin la base, i totes les lectures de la línia comparteixen línia de base; una lectura més ampla que la seva base eixampla la caixa de la base, menys el quart d'em de ruby que pot envair un veí sense lectura, i dues lectures deixen un quart d'em de ruby entre elles (dues lectures en zhuyin, un quart de l'em dels seus símbols, de manera que tres símbols al costat de cadascun de dos caràcters no surten de la seva casella). Un ruby mono (una lectura per caràcter) es pot tallar entre els seus caràcters; un ruby de grup, mai. Al principi o al final d'una línia, base i lectura s'alineen amb la vora (clreq §5.5.4). El zhuyin en text horitzontal forma una columna a la dreta de cada caràcter, i la caixa del caràcter creix el que mesura la columna; en text vertical la mateixa columna baixa a la dreta dels caràcters. La marca de to va a la dreta de la columna, amb la meitat del seu traç per sobre de l'últim símbol (clreq §5.5.3.3), col·locada pel seu traç perquè les fonts dibuixen aquestes marques altes dins de la seva caixa, i en vertical es manté dreta, igual que el punt del to neutre sobre el primer símbol.
- Notes warichu (双行夹注): es pleguen en dues files a la mida de la nota, centrades a la línia i sense espai entre elles. La fila de dalt pren caràcters fins a tenir almenys la meitat del fragment, de manera que la segona no és mai la més llarga, i un més mentre la de baix començaria amb un signe que no pot obrir línia. Una nota més llarga que l'espai que queda a la línia l'omple i continua a la línia, la columna o la pàgina següents; els seus signes van només abans de la primera fila i després de l'última. En text vertical la fila de dalt és la de la dreta, que es llegeix primer.
El pas de línia no canvia mai: marques i lectures viuen a l'interlineat. Un paràgraf l'espai entre línies del qual (l'alçada de línia menys el cos) mesura menys de mig em amb marques per un costat, o cinc vuitens amb marques pels dos (clreq §5.6.1), s'avisa amb cjkMarksExceedLeading; un les lectures del qual mesuren més que l'espai (una lectura en pinyin, amb el que puja), amb rubyExceedsLeading. Dona a aquests paràgrafs un estil de paràgraf amb més interlineat.
Al VDT un segment marcat porta cjkMarks, i la composició col·loca els punts i les línies de les seves línies a VDTLine.marks (punts, cercles, sèsams, línies rectes i ondulades, en el marc de flux de la línia), que el canvas, l'HTML i el PDF dibuixen tal qual (PDF: Artifact /Layout; HTML: caixes aria-hidden i el text amb punts dins <em>). El segment de la base d'un ruby porta ruby (la lectura i els seus trossos) i pinta la base a inkOffset quan la lectura l'ha eixamplada; el fragment d'una nota warichu en una línia és un segment el text del qual és la fila de dalt i després la de baix, i el warichu del qual guarda les files que els renderitzadors pinten al seu lloc. El PDF etiquetat posa la lectura al RT del Ruby de la seva base i la nota en un Warichu, i l'/ActualText de la línia llegeix el text de la base i la nota una sola vegada. Les marques, lectures i notes es dibuixen al text, els títols, les llistes i els requadres; els peus de figura, les cel·les de taula i el text de disseny conserven el text sense elles.
El resolutor i el depurador segueixen el patró de les altres seccions; setCjkLineBreak i setCjkComposition fixen el nivell i la composició (amplades de la puntuació, puntuació penjada, espai entre xinès i llatí; cjkCompositionOf(resolved.cjk, dpi)) per a les mesures fetes fora de buildDocument, que els pren de la configuració. Fora d'una composició, els signes conserven tot el seu avanç i no es posa espai entre xinès i llatí:
import { DEFAULT_CJK_CONFIG, resolveCjkConfig, stripCjkDefaults, cjkRegionOf, setCjkLineBreak, setCjkComposition, cjkCompositionOf } from 'postext';
resolveCjkConfig(undefined, 'zh-HK');
// { region: 'hongkong', lineBreak: 'basic', punctuationWidth: 'fullwidth', compressAdjacent: true,
// trimLineStart: true, hangingPunctuation: 'none', latinSpacing: { value: 0.25, unit: 'em' }, uprightDigits: 2,
// grid: { enabled: false, charsPerLine: 0, linesPerPage: 0, show: false },
// emphasis: 'dots', bookTitleMark: 'wavy',
// ruby: { fontSize: { value: 0.5, unit: 'em' }, position: 'auto' },
// warichu: { fontSize: { value: 0.5, unit: 'em' }, open: '', close: '' } }Al Sandbox aquesta configuració és a Disseny › Escriptura › Tipografia d'Àsia oriental.
#Tipus de recurs
Un tipus de recurs és una categoria definible per l'usuari — Figura, Taula, Diagrama, Llistat… — que determina com es numeren, com es retola el peu i com es referencien els recursos d'aquella classe. La llista viu a config.resourceTypes; el Sandbox l'edita a Disseny → Figures i taules → Numeració i col·locació.
Quan config.resourceTypes no està definit, Postext inclou dos valors per defecte integrats: Figura i Taula, tots dos numerats com {h1}.{n} (es reinicien a cada encapçalament de nivell 1) amb comptadors decimals. S'anomenen en la llengua del document: config.locale o, si no, bodyText.hyphenation.locale o, si no, l'anglès (vegeu Llengua del document).
Els valors per defecte integrats s'adapten a la llengua. La funció exportada defaultResourceTypes(locale = 'en') localitza els noms de tipus, les etiquetes curtes i els prefixos de peu a la llengua del document — l'anglès produeix Figure/Fig. i Table/Tab.; el castellà produeix Figura/Fig. i Tabla/Tabla; el francès, l'alemany, l'italià, el portuguès, el català i el neerlandès tenen els seus (la taula de Llengua del document els recull). Les etiquetes regionals com es-ES es resolen per llengua, i qualsevol llengua sense traducció passa a l'anglès. El comportament de numeració (numberingTemplate: '{h1}.{n}', resetOn: 'h1', comptadors decimals) és independent de la llengua. Cada crida retorna objectes nous, de manera que pots mutar el resultat lliurement:
import { defaultResourceTypes } from 'postext';
const types = defaultResourceTypes('es');
// => [{ id: 'figure', name: 'Figura', shortLabel: 'Fig.', captionPrefix: 'Figura',
// numberingTemplate: '{h1}.{n}', resetOn: 'h1', counterFormat: 'decimal', … },
// { id: 'table', name: 'Tabla', shortLabel: 'Tabla', captionPrefix: 'Tabla', … }]type ResourceCounterFormat =
| 'decimal'
| 'roman-lower'
| 'roman-upper'
| 'alpha-lower'
| 'alpha-upper';
type ResourceCounterReset = 'never' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
interface ResourcePlacement {
position?: 'auto' | 'top' | 'bottom' | 'here'; // quin espai lliure pot ocupar el flotant; 'here' = incrustat en línia a la directiva ::resource
span?: 'column' | 'page' | 'side'; // una columna, tota l'amplada de contingut o la columna lateral només per a flotants
rotate?: 'ccw' | 'cw'; // un quart de volta: una taula apaïsada en una pàgina pròpia
width?: number; // fracció (0 < width < 1) de l'amplada de la columna o de la pàgina; per defecte, tota l'amplada
align?: 'left' | 'center' | 'right'; // on se situa un flotant més estret que la seva columna; per defecte 'left'
captionSide?: boolean; // peu al costat de la figura, a la columna lateral d'una disposició oneAndHalf (només flotants de columna)
}
interface ResourceType {
id: string; // id estable, referenciat per Resource.typeId
name: string; // nom singular, p. ex. "Figura"
namePlural?: string; // plural opcional, p. ex. "Figures"
shortLabel: string; // etiqueta curta per a refs en línia, p. ex. "Fig."
numberingTemplate: string; // "{h1}.{n}" o "{n}"
resetOn: ResourceCounterReset; // quan es reinicia el comptador {n}
counterFormat: ResourceCounterFormat;// com es formata {n}
captionPrefix: string; // prefix del peu, p. ex. "Figura"
defaultPlacement?: ResourcePlacement;// col·locació de reserva per als recursos d'aquest tipus
}ResourcePlacement té la mateixa forma que el placement que un recurs defineix pel seu compte. position tria la classe d'espai lliure que pot ocupar el flotant — auto (el valor per defecte) pren el primer després de la primera referència, top / bottom el limiten a aquella classe de banda, here incrusta el recurs en línia. span fixa l'extensió del flotant: una columna, tota l'amplada de contingut o la columna lateral només per a flotants d'una disposició de columna i mitja. rotate gira el recurs un quart de volta i el converteix en un flotant d'amplada de pàgina en una pàgina pròpia. width estreny el flotant a una fracció de la seva columna (o de la pàgina, en un flotant d'amplada de pàgina) — una taula petita en una columna ampla, per exemple. align indica on se situa aquest flotant més estret — a l'esquerra per defecte, centrat o a la dreta — i on se situa dins el seu espai una imatge més estreta que ell: un mapa de bits més petit que la columna, o una imatge que layout.fitFiguresToPage ha reduït. El peu i la nota conserven la mesura de l'espai. (Fins a postext 1.4 aquesta imatge es componia sempre alineada a l'esquerra). captionSide posa el peu al costat de la figura a la columna lateral només per a flotants d'una disposició de columna i mitja (layout.sideColumnRole: 'floats'), a l'alçada de la vora superior de la figura (de la inferior en un flotant de peu); només s'aplica als flotants de columna, i una pàgina sense aquella columna manté el peu sota la figura. Quan ni el recurs ni el seu tipus defineixen una col·locació, el valor integrat és auto / column.
| Propietat | Tipus | Descripció |
|---|---|---|
id | string | Identificador estable referenciat pel typeId de cada recurs. Es fixa en crear el tipus; esborrar un tipus al qual encara apunten recursos genera un avís de tipus penjant. |
name | string | Nom singular. El fa servir la referència en línia amb style="full" (p. ex. Figura 1.7). |
namePlural | string (opcional) | Nom plural, per a etiquetes de la interfície i llistes de recursos. |
shortLabel | string | Abreviatura compacta que fa servir l'estil de referència en línia per defecte (p. ex. Fig. 1.7). |
numberingTemplate | string | Plantilla del número calculat. Vegeu Tokens de plantilla més avall. Les formes habituals són (àmbit de capítol, p. ex. 2.3) i (un únic recompte continu). |
resetOn | ResourceCounterReset | 'never' dona un recompte continu per a tot el document; 'h1'..'h6' reinicien el comptador cada vegada que es troba un encapçalament d'aquell nivell (o de qualsevol avantpassat). Ajusta'l perquè coincideixi amb el nivell d'encapçalament que apareix a la plantilla — p. ex. amb resetOn: 'h1'. |
counterFormat | ResourceCounterFormat | Com es renderitza el comptador : decimal (1, 2, 3), romà minúscul/majúscul (i, ii / I, II) o alfabètic minúscul/majúscul (a, b / A, B). També s'accepten les grafies de pàgines i llistes ('lower-roman', 'arabic'…; vegeu Grafies dels formats de numeració); un valor desconegut compta en decimal i es notifica. Els tokens d'encapçalament (…) es renderitzen sempre com a decimals. |
captionPrefix | string | Text que s'anteposa al peu de figura/taula. El número calculat segueix el prefix — un peu es renderitza com . , p. ex. Figura 1.7. El plànol original. Un tipus amb numberingTemplate buit no porta número, i el seu peu es llegeix . . Els espais al final del prefix es treuen, i un prefix que ja acaba en ., :, !, ? o … (o en la seva forma d'amplada completa) no porta un segon punt: Làm. Línies a 0°. |
defaultPlacement | ResourcePlacement (opcional) | Col·locació que fan servir els recursos d'aquest tipus que no defineixen el seu propi placement: position, span, rotate, width, align i captionSide, cadascun resolt per separat. Quan ni el recurs ni el tipus defineixen un camp, s'aplica el valor integrat: auto / column, sense girar, tota l'amplada, alineat a l'esquerra, peu sota la figura. Vegeu Numeració i referències més avall per a la cadena de resolució i Format del document › Recursos per al que fa cada valor, inclosos els recursos girats. |
captionStyle | CaptionStyleConfig (opcional) | Sobreescriptura parcial de l'estil de peus per als recursos d'aquest tipus. Només les claus que indiquis substitueixen el captionStyle global; la resta s'hereta (un color sobreescrit arrossega també el color de l'etiqueta i de la nota llevat que es fixin explícitament). Ús típic: taules amb el peu a sobre damunt una barra de color mentre les figures conserven el peu a sota. Les referències a la paleta es resolen com qualsevol altre color. |
#Tokens de plantilla
numberingTemplate es renderitza amb el mateix motor que la numeració d'encapçalaments (vegeu Encapçalaments). Reconeix dues classes de token:
{n}— el comptador per tipus, formatat segonscounterFormat. És el valor que s'incrementa per recurs i es reinicia segonsresetOn.{h1}…{h6}— els números d'encapçalament vigents al punt de la primera referència, renderitzats sempre com a decimals.{h1}és el número de l'encapçalament de nivell 1 actual,{h2}el de nivell 2, i així successivament.
Qualsevol altre text és literal. Una barra inversa escapa un {, } o \ literal. Quan un token d'encapçalament no té valor en el seu àmbit (p. ex. {h1} abans de qualsevol encapçalament de nivell 1), col·lapsa juntament amb el separador adjacent — així {h1}.{n} es redueix sense problemes al comptador sol.
Una plantilla buida ('') no imprimeix número, encara que el tipus continuï comptant els seus recursos: el peu es llegeix Detall. Línies a 0° i un :ref imprimeix només l'etiqueta (Detall). Fins a postext 1.4 aquest peu es llegia Detall .Línies a 0° i la referència acabava en un espai de no separació.
| Plantilla | Amb h1 = 2, comptador = 3 | Notes |
|---|---|---|
| 3 | Un únic recompte continu. Combina'l amb resetOn: 'never'. |
| 2.3 | Àmbit de capítol. Combina'l amb resetOn: 'h1'. |
| 2.0.3 | Àmbit de secció. Combina'l amb resetOn: 'h2'. |
#Numeració i referències
El número que produeix un tipus de recurs és el que imprimeix :ref i el que segueix el prefix del peu. :ref{id} és la forma principal: la primera referència en ordre de lectura incorpora el recurs, que flota al primer espai lliure després d'aquella referència — el final de la columna de la referència, el principi o el final de la columna buida següent, o una banda de la pàgina següent (segons la seva col·locació resolta — position: 'auto' | 'top' | 'bottom' | 'here' i span: 'column' | 'page' | 'side', més rotate, width, align i captionSide, resolta per recurs, després pel defaultPlacement del seu tipus, i finalment pel valor integrat auto / column; 'top' / 'bottom' limiten la cerca a espais d'aquell tipus). La inserció en bloc ::resource{id} és opcional i només cal per a placement.position: 'here' — una inserció en línia, sense flotar, en un punt exacte del flux. La gramàtica completa del costat del document — totes dues formes més les opcions style i text de :ref — es documenta a Format del document › Recursos, inclòs com l'ordre de primera referència determina el recompte.
Això reflecteix la numeració d'encapçalaments: igual que un nivell d'encapçalament porta un numberingTemplate, un tipus de recurs també en porta — però el comptador del recurs ({n}) avança per primera referència en lloc de per encapçalament, i resetOn el lliga de nou a la jerarquia d'encapçalaments.
#Què es numera
Un recurs es numera quan el text el referencia — amb :ref o amb una inserció ::resource —, en l'ordre d'aquestes primeres referències i sigui quina sigui la seva col·locació: flotant, en línia (here), a la columna lateral o girat. Un recurs que només dibuixa un disseny — un element image d'una obertura de capítol, d'una capçalera o d'una pàgina de part — o que res no referencia no porta número ni fa avançar el comptador del seu tipus. Així, en un assaig fotogràfic les làmines a sang del qual són imatges de les obertures i l'única làmina menor del qual és un flotant citat, aquest flotant és la làmina I, per moltes làmines que hagin mostrat abans les obertures; numera les làmines de les obertures en el seu disseny (amb un atribut com {attr.plate}) i deixa el comptador per a les làmines que cita el text.
En un llibre que es maqueta capítol a capítol (el Sandbox, buildBundle, o buildDocument amb els comptadors que lliura continuationAfter()), compta la primera referència de tot el llibre: un recurs conserva el número que va rebre al capítol que el cita per primera vegada, i només aquell capítol el col·loca. El :ref d'un capítol posterior imprimeix aquell número i no col·loca res, i una inserció ::resource d'un recurs flotant hi és una referència més (una inserció here en línia es continua component on està escrita). Aquesta referència enllaça amb la figura quan la figura és a la mateixa sortida: un PDF del llibre sencer l'enllaça amb la pàgina del capítol anterior. Un capítol que es renderitza sol, en HTML o en PDF, la compon com a text sense enllaç, en el color dels enllaços, perquè la seva figura no és en aquell document. Un amfitrió que uneix en una sola pàgina l'HTML dels capítols passa a renderToHtml els recursos que ancoren els capítols, com a refTargets, i aquesta referència torna a enllaçar amb la figura del capítol anterior:
import { anchoredResourceIds, buildBundle, renderToHtml } from 'postext';
const docs = buildBundle(bundle);
const refTargets = new Set(docs.flatMap((d) => [...anchoredResourceIds(d)]));
const html = docs.map((d) => renderToHtml(d, { refTargets })).join('');Canvia a postext 1.5: fins a la 1.4, cada capítol que citava una figura la tornava a col·locar com a flotant, i l'HTML de tot :ref era un enllaç, tant si la seva figura era a la pàgina com si no.
{h1} és el recompte dels encapçalaments de nivell 1: tot H1 el fa avançar llevat que el seu estil d'encapçalament fixi numbered: false — un numberingTemplate buit amaga el número de l'encapçalament, no atura el recompte. Per això un article l'únic H1 del qual és el seu títol numera les figures 1.1, 1.2… amb els tipus integrats {h1}.{n}. Hi ha dues maneres d'imprimir Figura 1, 2…:
- un tipus numerat
{n}ambresetOn: 'never'(ambresetOn: 'h1'el recompte tornaria a començar a cada H1); - un estil d'encapçalament amb
numbered: falseal títol, i a qualsevol altre H1 que no hagi de comptar: un encapçalament així no fa avançar{h1}, sinó que el deixa com estava — buit abans del primer H1 que compta, on{h1}.{n}es redueix al comptador sol — i mai no dispararesetOn: 'h1', de manera que el recompte continua a través seu. Després de# Introduccióni la seva figura 1.1, la primera figura sota un# Apéndicesense número és la 1.2, no la 2.1.
// Figura 1, 2, 3… en un document d'un sol article
resourceTypes: defaultResourceTypes('es').map((t) => ({ ...t, numberingTemplate: '{n}', resetOn: 'never' })),#Estil de taules
La propietat tableStyle controla la tipografia i la decoració de les taules-recurs —de totes, llevat de les que trien un estil de taula amb nom—. Les cel·les de cos i de capçalera s'estilitzen de manera independent. La família tipogràfica, la mida i els colors hereten del text de cos resolt quan no s'indiquen, de manera que un document sense tableStyle renderitza les taules amb la tipografia del cos.
const config: PostextConfig = {
tableStyle: {
headerBold: true,
headerBackground: { hex: '#f0f0f0', model: 'hex' },
borders: true,
borderWidth: { value: 0.75, unit: 'pt' },
},
};| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
bodyFontFamily | string | font del cos | Família tipogràfica de les cel·les de cos. |
bodyFontSize | Dimension | mida del cos | Mida de font de les cel·les de cos. |
bodyColor | ColorValue | color del cos | Color del text de les cel·les de cos. |
headerFontFamily | string | font del cos | Família tipogràfica de les cel·les de capçalera. |
headerFontSize | Dimension | mida del cos | Mida de font de les cel·les de capçalera. |
headerColor | ColorValue | color del cos | Color del text de les cel·les de capçalera. |
headerBold | boolean | true | Renderitzar les cel·les de capçalera en negreta. |
headerItalic | boolean | false | Renderitzar les cel·les de capçalera en cursiva. |
headerLetterSpacing | Dimension | 0pt | Espaiat després de cada caràcter d'una cel·la de capçalera, espais inclosos, com el letter-spacing de CSS. Un valor positiu obre les lletres (una capçalera en majúscules sol demanar entre 0.05em i 0.1em) i un de negatiu les estreny. Un em és el cos de la capçalera. Les línies de capçalera es mesuren amb ell, de manera que tallen, es centren i s'alineen amb l'espaiat, i canvas, HTML i PDF el pinten igual. S'aplica a totes les cel·les de capçalera: les files de capçalera i qualsevol cel·la marcada amb isHeader. |
headerTextTransform | 'none' | 'uppercase' | 'none' | Compon les cel·les de capçalera en majúscules. El text conserva la seva longitud, de manera que el Sandbox continua associant cada lletra a la font: una lletra la majúscula de la qual és més llarga (ß) es queda com està. Les referències a recursos conserven la seva etiqueta. |
headerBackgroundEnabled | boolean | true | Pintar un emplenament darrere la fila de capçalera. |
headerBackground | ColorValue | #f0f0f0 | Color d'emplenament de la fila de capçalera. |
bodyBackgroundEnabled | boolean | false | Pintar un emplenament darrere les files de cos. |
bodyBackground | ColorValue | #ffffff | Color d'emplenament de les files de cos (només es pinta quan està activat). |
bodyAlternateBackgroundEnabled | boolean | false | Files alternes: omplir una de cada dues files de cos amb bodyAlternateBackground. Vegeu files alternes. |
bodyAlternateBackground | ColorValue | #f2f2f2 | Color d'emplenament de les files alternes (només es pinta quan està activat). |
borders | boolean | true | Dibuixar les vores de les cel·les. |
borderColor | ColorValue | color del cos | Color del traç de les vores. |
borderWidth | Dimension | 0.75pt | Gruix del traç de les vores (≈1px a 96 DPI; escala amb els DPI de la pàgina). |
cellPadding | Dimension | 0.375em | Farciment interior de cada cel·la. |
rules | 'grid' | 'horizontal' | 'outer' | 'none' | 'grid' | Quins filets traçar quan borders està actiu: la retícula completa de cel·les, només filets horitzontals (vora superior i inferior de cada fila, sense verticals), només el marc exterior, o cap. |
borderRadius | Dimension | 0 | Radi de les cantonades del marc exterior de la taula. El marc es traça arrodonit (amb els filets grid o outer), els fons de les cel·les i de la capçalera s'hi retallen —també amb rules: 'none' o sense vores— i els filets horitzontals es retallen al seu contorn exterior; els filets interiors continuen rectes. Una taula partida entre pàgines arrodoneix les cantonades superiors de la primera part i les inferiors de l'última. Es limita a la meitat de l'amplada i de l'alçada de la taula. |
overflow | 'split' | 'clip' | 'hide' | 'split' | Què passa amb una taula més alta que la pàgina: continuar-la a les pàgines següents, conservar només les files que hi caben, o no col·locar-la. Vegeu més avall. |
continuedSuffix | string | '(cont.)' | S'afegeix, en cursiva, al peu de cada part continuada d'una taula dividida, després d'un espai; un sufix que comença per un caràcter xinès o d'amplada completa ('(续)') va enganxat al peu. |
continuesMarkerEnabled | boolean | true | Col·loca un indicador sota cada part que continua a la pàgina següent. |
continuesMarker | string | 'Continúa' / 'Continued' | Text d'aquest indicador, alineat a la dreta sota la part amb la tipografia de la nota (vegeu estil de peus). El valor per defecte segueix la llengua del document (vegeu Llengua del document per a les vuit llengües). |
Els gruixos de vora es conserven fraccionaris: un filet de 0.5pt es traça com a línia fina al PDF i a la pantalla en lloc d'arrodonir-se a un píxel complet (el mínim és 0.25px).
#Files alternes
Una taula de dades llarga se segueix millor d'un costat a l'altre quan una de cada dues files va ombrejada. bodyAlternateBackgroundEnabled activa les franges i bodyAlternateBackground en fixa el color:
const config: PostextConfig = {
tableStyle: {
bodyBackgroundEnabled: true,
bodyBackground: { hex: '#ffffff', model: 'hex' },
bodyAlternateBackgroundEnabled: true,
bodyAlternateBackground: { hex: '#eef3fa', model: 'hex' },
},
};Les files es compten des de la primera després de les files de capçalera (TableModel.headerRowCount, o les files inicials formades per cel·les de capçalera): aquesta fila conserva bodyBackground —o cap emplenament mentre bodyBackgroundEnabled està desactivat—, la següent pren l'emplenament alternat, i així successivament. El recompte segueix el model de la taula, no la pàgina, de manera que una taula partida entre pàgines conserva la franja de cada fila en totes, i una cel·la combinada al llarg de diverses files pren la franja de la primera. Les cel·les de capçalera conserven l'emplenament de capçalera, el background propi d'una cel·la preval sobre tots dos i un color vinculat a la paleta segueix la paleta. Un estil de taula amb nom defineix els dos camps com qualsevol altre, de manera que un estil pot portar franges mentre les altres taules del document no; al Sandbox són l'interruptor Files alternes i el seu color, a Cel·les del cos.
Al VDT, les cel·les de les files alternes porten alternate: true i la maquetació de la taula porta bodyAlternateBackground. tableCellFill(table, cell) retorna l'emplenament amb què es pinta una cel·la —el seu, el de capçalera, l'alternat o el de cos—, que és el que pinten els backends de canvas, HTML i PDF. Els emplenaments veïns s'ajunten sense costura: un navegador amb una densitat de píxels fraccionària o un visor de PDF suavitzen cada emplenament per separat i deixarien veure el paper en un filet finíssim entre dues cel·les, així que els backends d'HTML i PDF pinten tableCellFillRects(table) —l'emplenament de cada cel·la amb una franja sobre cada vora que comparteix amb una cel·la pintada després, que aquesta cel·la després cobreix— i el canvas ajusta els seus emplenaments als píxels del dispositiu.
#Estils de taula amb nom
Un document poques vegades compon totes les seves taules igual: una llista de comprovació en una quadrícula blau marí amb el marc arrodonit, una fila d'opcions tancada només pel seu marc exterior, una taula de dades amb filets horitzontals. tableStyles declara variants amb nom, i un recurs de taula en tria una amb table.styleId. Cada camp que un estil deixa sense definir es llegeix primer de tableStyle i després del text de cos, de manera que un estil només indica el que distingeix les seves taules. Una taula sense styleId, o amb un id que cap estil no declara, conserva tableStyle: un document sense tableStyles es compon exactament igual que abans.
const config: PostextConfig = {
tableStyle: {
borderColor: { hex: '#163a76', model: 'hex' },
borderWidth: { value: 1.3, unit: 'pt' },
borderRadius: { value: 10, unit: 'pt' },
},
tableStyles: [
{
id: 'option',
name: "Fila d'opcions",
rules: 'outer',
borderColor: { hex: '#7a9cc6', model: 'hex' },
borderWidth: { value: 1, unit: 'pt' },
borderRadius: { value: 8, unit: 'pt' },
headerBackgroundEnabled: false,
},
],
};
// Als recursos: aquesta taula es compon amb l'estil "option".
const resource: Resource = {
id: 'choices', typeId: 'table', kind: 'table', createdAt: 0, updatedAt: 0,
table: { model: { rows: [/* … */] }, styleId: 'option' },
};Cada entrada admet tots els camps de tableStyle més id (el que referencia table.styleId) i un name opcional per a l'editor (per defecte, l'id). Tot el que un estil pot definir s'aplica per taula: tipografia, fons, vores, filets, radi de les cantonades, farciment i el comportament de desbordament amb les seves cadenes de continuació. resolveTableStylesConfig(styles, tableStyle, resolvedBodyText, locale?) retorna la llista resolta, pickTableStyle(resolved, styleId) l'estil amb què es compon una taula, i stripTableStylesDefaults elimina els camps sense definir (conserva un camp igual al seu valor per defecte, que continua substituint un valor diferent de tableStyle).
#Taules més altes que la pàgina
Una taula flotant que no cap a la pàgina nova que se li ofereix no es comprimeix ni es desborda: amb overflow: 'split' (el valor per defecte) el motor la talla entre files a l'última vora que cap a la pàgina i la continua a les pàgines següents, tantes com calgui. Cada part continuada repeteix les files de capçalera de la taula (TableModel.headerRowCount, o les files inicials formades per cel·les de capçalera quan no està definit) i torna a portar el peu amb continuedSuffix després de la descripció: «Taula 6-4. Títol (cont.)». Cada part que en segueix una altra porta continuesMarker a sota, alineat a la dreta, amb la tipografia de la nota; la nota de la taula es reserva per a l'última part. Un tall no travessa mai una cel·la combinada (una cel·la amb rowSpan passa sencera a la part següent), i una fila que encapçala les que la segueixen —una sola cel·la a l'amplada de tota la taula— passa a la part següent en lloc de quedar solta al peu de la pàgina.
On acaba la primera part. Una taula a la qual s'ofereix el cap d'una columna buida després de la seva referència pren les files que hi caben i continua en el buit següent. Omple la columna fins al peu quan la té per a ella sola: una part que deixaria menys de tres línies de text a sota també les ocupa, en lloc de deixar un residu de text solt. Quan la columna ja té una altra banda de flotants —una figura a l'amplada de pàgina al cap, per exemple—, la part s'atura almenys tres línies de text abans del peu, l'espai per a text que deixa qualsevol flotant que comparteix columna amb un altre, de manera que la columna acaba amb una mica de text sota la taula i no només amb flotants. Perquè una taula llarga arribi al peu de la seva columna, cita-la on la pàgina en què comença no porti cap altre flotant (després de la pàgina d'una figura a l'amplada de pàgina, per exemple), o ajusta'n les files a la columna.
'clip' conserva les files inicials que caben a la pàgina i descarta la resta en silenci (la nota continua tancant la part); 'hide' no col·loca la taula. Tots dos només actuen quan la taula és més alta que una pàgina: una taula que hi cap es col·loca sencera en qualsevol mode. Les taules en línia (placement.position: 'here') no es divideixen.
Les cadenes de continuació prenen el valor per defecte de la llengua del document (locale, o si no n'hi ha, la llengua de la partició de mots): anglès (cont.) / Continued, castellà (cont.) / Continúa, i el mateix en francès, alemany, italià, portuguès, català i neerlandès (recollides a Llengua del document).
El contingut d'una cel·la és markdown en línia, i un salt de línia dins de la cel·la —un caràcter de línia nova, o \\ com en peus i notes— obre un paràgraf nou. Un paràgraf que comença per una vinyeta o un guionet (•, -, *, –) o per un número (1., 1)) seguit d'un espai es compon com a element de llista: la marca es pinta tal com es va escriure, el text en penja a la distància unorderedLists.gap del document, les línies partides s'alineen amb el text i dos espais inicials nien un nivell. Així, una cel·la escrita com • Ofrece elección\n• Acomoda a personas diestras y zurdas surt com una llista de dos elements. Una línia amb només espais corrents no afegeix res; una línia amb un espai de no separació (U+00A0) és una línia de la cel·la, com a CommonMark, així que 1\n seguit d'un espai de no separació dona una fila de dues línies. Un espai de no separació al final del text d'una cel·la conserva la seva amplada: 760 i un espai de no separació, alineats a la dreta sobre (231), acaben un espai abans de la vora, cosa que acosta el 0 a l'1. Les xifres només queden exactament en columna si l'espai és tan ample com el parèntesi, i en la majoria de les fonts és més estret. (Fins a postext 1.4 es perdien tots dos).
Les amplades de columna pertanyen al model de la taula, no a l'estil: TableModel.columnWidths és un array opcional de pesos relatius, un per columna, normalitzats en el moment de maquetar —[2, 1, 1] dona a la primera columna la meitat de l'amplada. Un array absent, de longitud incorrecta o amb algun pes no positiu torna al repartiment igualitari. L'editor de taules manté l'array alineat en afegir o treure columnes.
#Construir models de taula
Un TableModel és una quadrícula per files, i cada cel·la es maqueta segons la seva posició en aquesta quadrícula: rows[r][c] ocupa la columna c. Per això una cel·la combinada conserva a la quadrícula les cel·les que cobreix, cadascuna marcada amb hiddenBy apuntant a la seva cel·la principal, a diferència d'una taula HTML, que les omet. Les funcions de model que exporta postext mantenen aquesta forma; són funcions pures que retornen un model nou: mergeCells(model, { start, end }) i unmergeCell(model, at), addRow, addColumn, removeRow, removeColumn, setCellContent, setCellImage, setCellBackground i setAlignment. Les quatre funcions de files i columnes mantenen senceres les combinacions: una fila o columna afegida dins d'un bloc combinat l'eixampla, una afegida abans el desplaça i una que se n'elimina el redueix —un bloc que perd la primera fila o columna conserva el contingut a la nova cel·la superior esquerra—, i cada hiddenBy continua apuntant a la seva cel·la principal.
parseTSV(text, options?) construeix un model a partir de text separat per tabuladors —un rang enganxat des d'un full de càlcul—: les files se separen per salts de línia, les cel·les per tabuladors, i les files curtes s'omplen perquè la quadrícula sigui rectangular. headerRows converteix les files inicials en files de capçalera: les seves cel·les reben isHeader i el model headerRowCount, de manera que una taula partida entre pàgines les repeteix.
import { parseTSV, mergeCells } from 'postext';
let model = parseTSV('Peça\tQuant.\tNota\nCargol\t4\tM6\nFemella\t8\t', { headerRows: 1 });
// model.headerRowCount === 1; model.rows[0][0] és { content: 'Peça', isHeader: true }
model = mergeCells(model, { start: { row: 2, col: 1 }, end: { row: 2, col: 2 } });
// rows[2][1] rep colSpan: 2; rows[2][2] continua a la quadrícula amb hiddenBy: { row: 2, col: 1 }tableGridIssues(model) comprova la quadrícula. Retorna una llista buida per a un model correcte i, si no, cada punt en què la quadrícula es trenca, per ordre de files: spanOverlap, una cel·la visible sota el colSpan / rowSpan d'una altra (coveredBy anomena aquesta cel·la) —cosa que passa en ometre una cel·la coberta a l'estil HTML, perquè totes les cel·les que la segueixen es desplacen sobre la combinació— i missingCells, una fila que acaba abans de l'última columna sense que cap combinació en cobreixi la resta, cosa que deixa un buit.
import { tableGridIssues } from 'postext';
tableGridIssues({
rows: [
[{ content: 'A', colSpan: 2 }, { content: 'C' }],
[{ content: '1' }, { content: '2' }, { content: '3' }],
],
});
// => [{ kind: 'spanOverlap', row: 0, col: 1, coveredBy: { row: 0, col: 0 } },
// { kind: 'missingCells', row: 0, col: 2 }]Una taula que el document fa servir i la quadrícula de la qual té aquests problemes es notifica a doc.contentWarnings com a raggedTableGrid (vegeu Avisos del document).
#Estil dels peus de recurs
La propietat captionStyle controla els peus de recurs (la línia Figura 1 — … sota —o sobre— imatges, SVG i taules). L'etiqueta numerada i la descripció comparteixen tipografia i mida —una limitació del motor—, però l'etiqueta pot portar el seu propi pes, cursiva i color. La família tipogràfica, la mida i el color hereten del text del cos quan no s'indiquen. El peu es pot situar damunt del recurs (la convenció habitual per a taules) i compondre's sobre una barra de color que ocupa tota l'amplada del bloc; una nota opcional més petita (font, crèdits — Resource.note) s'estilitza mitjançant el subobjecte note. Un tipus de recurs pot sobreescriure qualsevol d'aquests camps per als seus propis recursos mitjançant ResourceType.captionStyle (vegeu Tipus de recurs).
const config: PostextConfig = {
captionStyle: {
align: 'center',
labelBold: true,
labelColor: { hex: '#295AA3', model: 'hex' },
descriptionItalic: true,
position: 'above',
backgroundEnabled: true,
padding: { value: 0.35, unit: 'em' },
note: { italic: true, align: 'left' },
},
};| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily | string | font del cos | Família tipogràfica del peu (etiqueta i descripció). |
fontSize | Dimension | mida del cos | Mida de font del peu (etiqueta i descripció). |
color | ColorValue | color del cos | Color del text de la descripció. |
align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'left' | Alineació horitzontal de les línies del peu. 'justify' porta totes les línies llevat de l'última a l'amplada completa. Sobre una barra de peu les línies s'alineen dins del seu farciment; un peu lateral s'alinea dins de la seva pròpia amplada. |
gap | Dimension | 0.75em | Separació vertical entre el recurs i el seu peu. |
labelBold | boolean | true | Renderitzar l'etiqueta numerada (p. ex. Figura 1) en negreta. |
labelItalic | boolean | false | Renderitzar l'etiqueta numerada en cursiva. |
labelColor | ColorValue | color del peu | Color de l'etiqueta numerada. |
descriptionItalic | boolean | false | Renderitzar el text de la descripció en cursiva. |
position | 'above' | 'below' | 'below' | On se situa el peu. Amb 'above' el peu (i la seva barra) va primer i el cos del recurs baixa l'alçada del peu més gap; la nota passa llavors sota el cos. |
backgroundEnabled | boolean | false | Pintar una barra darrere del peu. La barra ocupa tota l'amplada del bloc i embolcalla les línies del peu més padding per cada costat. |
background | ColorValue | color principal de la paleta | Color d'emplenament de la barra (només es pinta quan està activada). |
padding | Dimension | 0.35em | Farciment interior entre la vora de la barra i el text del peu. S'ignora si la barra està desactivada. |
note | object | — | Estil de la nota del recurs — vegeu la subtaula següent. |
labelNumberGap | string | espai de no separació | El que separa l'etiqueta del número, al peu i en una :ref en línia: Figura 1.7, Fig. 1.7. El xinès els compon junts: '' dona 图1-1. |
labelSeparator | string | '. ' | El que segueix el número, abans de la descripció: Figura 1.7. Un peu. Els peus xinesos porten un espai ideogràfic, ' ' (图1-1 标题). Una etiqueta sense número conserva la seva pròpia regla: un punt, llevat que el prefix ja acabi en punt. |
El subobjecte note estilitza Resource.note, una línia breu (font, crèdits, una observació) composta sota el recurs en una mida menor. Admet el mateix format en línia i les mateixes marques :ref que el peu, i hereta la tipografia del peu. Es col·loca sota el peu quan aquest va a sota, i sota el cos del recurs quan el peu va a sobre; la seva alçada compta en el bloc, de manera que un recurs amb nota flota com una sola unitat.
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
note.fontSize | Dimension | 0.85 × mida del peu | Mida de font de la nota. |
note.color | ColorValue | color del peu | Color del text de la nota. |
note.italic | boolean | false | Renderitzar la nota en cursiva. |
note.gap | Dimension | 0.35em | Separació entre la nota i el que la precedeix (peu o cos). |
note.align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'left' | Alineació horitzontal de les línies de la nota, com align per al peu. |
Les sobreescriptures per tipus es combinen amb mergeCaptionStyle(resolvedCaptionStyle, override, palette?), exportada per als amfitrions que necessitin la mateixa resolució fora del pipeline.
#Estil dels diagrames
La propietat diagramStyle controla com s'acoloreixen els diagrames SVG incrustats (recursos amb kind: 'svg'). Avui la seva única funció és el mode d'una sola tinta: una passada de recoloració que converteix cada color del diagrama en un matís d'una única tinta, de manera que les figures es reprodueixin fidelment quan el document s'imprimeix amb una sola tinta plana.
const config: PostextConfig = {
diagramStyle: {
singleInk: true,
inkColor: { hex: '#295AA3', model: 'hex' },
},
};| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
singleInk | boolean | false | Recolorir tots els diagrames SVG incrustats amb matisos d'una única tinta. |
inkColor | ColorValue | Color principal (#295AA3) | La tinta. Per defecte és el color principal de la paleta del document (enllaçat mitjançant paletteId: 'main-color'), de manera que canviar la mostra de la paleta torna a tenyir els diagrames juntament amb els encapçalaments i les negretes. |
#Com funciona la tinta única
Quan singleInk està activat, cada color del marcatge SVG es reescriu com un matís de inkColor la intensitat del qual és 1 − luminància relativa (coeficients Rec. 709 aplicats als canals amb codificació gamma — una aproximació perceptual més que suficient per al mapatge de matisos). El mapatge preserva el valor percebut: el blanc es converteix en el blanc del paper, el negre en la tinta plena, i els emplenaments clars continuen sent clars amb independència del to original. Un fons groc pàl·lid passa a ser un matís pàl·lid de la tinta; un traç fosc s'acosta a la tinta plena.
La recoloració la fa la funció exportada applySingleInkToSvg(svgText, inkHex), que opera sense DOM, directament sobre el marcatge SVG com a text:
- Els literals hexadecimals
#rgb/#rgba/#rrggbb/#rrggbbaa, les funcionsrgb()/rgba()i les funcionshsl()/hsla()es reescriuen siguin on siguin — atributs de presentació,styleen línia, degradats,<defs>. Les funcions poden fer servir canals enters, decimals o en percentatge i la sintaxi amb comes o amb espais, així que també es recoloreixenrgb(11.37%, 20%, 50.59%)(com ho escriu Cairo) irgb(51 102 153 / 50%). Una funció tenyida es torna a escriure com argb(…)orgba(…). - Les paraules clau
whiteiblacknomés se substitueixen quan apareixen com a valors de pintura (fill,stroke,stop-color,flood-color,color— com a atributs o propietats d'estil en línia), mai dins del contingut de text ni de les etiquetes. none,transparenticurrentColores deixen intactes, igual que la resta de colors amb nom (red,steelblue…) i el negre per defecte d'una forma o un text que no fixa emplenament. Dona a aquests elements un color explícit perquè es recoloreixin.- Els canals alfa es preserven (els dígits de
#rgba/#rrggbbaai el component alfa dergba(…)viatgen sense canvis; un alfa en percentatge s'escriu com a número). - Quan
inkHexno es pot interpretar, l'entrada es retorna sense canvis. - El resultat porta
data-postext-single-ink="#…"(la tinta) al<svg>arrel, i un marcatge que ja el porta es retorna tal qual, sigui quina sigui la tinta que anomeni. El mapatge no és idempotent —una segona passada aclareix tots els colors, i el negre surt a uns dos terços de la tinta—, així que una imatge es recoloreix una sola vegada, tant si hi arriba abans el teu codi com el backend. (La marca és nova a postext 1.5; el marcatge recolorit per la 1.4 no la porta).
import { applySingleInkToSvg } from 'postext';
const recoloreado = applySingleInkToSvg(svgText, '#295AA3');
applySingleInkToSvg(recoloreado, '#295AA3') === recoloreado; // true: mai dues vegadesLa tinta única s'aplica als tres backends: el backend PDF recoloreix els bytes SVG que li lliura resourceBytes abans de dibuixar-los com a vectors, i els backends canvas i HTML tenyeixen les imatges SVG que pinten quan els ho demanes (consulta Tinta única en canvas i en HTML), de manera que el PDF exportat coincideix amb la previsualització en pantalla.
El resolver i l'stripper segueixen el mateix patró que les altres seccions, juntament amb els tipus DiagramStyleConfig / ResolvedDiagramStyleConfig:
import {
DEFAULT_DIAGRAM_STYLE_CONFIG,
resolveDiagramStyleConfig,
stripDiagramStyleDefaults,
applySingleInkToSvg,
} from 'postext';
import type { DiagramStyleConfig, ResolvedDiagramStyleConfig } from 'postext';
const resolved = resolveDiagramStyleConfig(config.diagramStyle);
// => { singleInk: false, inkColor: { hex: '#295AA3', model: 'hex', paletteId: 'main-color' } }
const minimal = stripDiagramStyleDefaults(config.diagramStyle);
// => undefined quan tot coincideix amb els valors per defecte#Tinta única en canvas i en HTML
Els backends canvas i HTML reben les imatges ja descodificades (registerResourceImage) o com a URL (resourceImageUrl), no el marcatge SVG. Si els ho demanes, apliquen el mateix mapatge al que dibuixen:
- Canvas (
renderPage,renderPageToCanvas,renderToCanvas). Tota imatge SVG a la qual s'aplica el tint —una figura, la imatge d'una cel·la de taula, una imatge de disseny, una icona omarkerde caixa— es rasteritza a la mida amb què es col·loca, i els seus píxels es tenyeixen amb la tinta, tant si es va registrar com a<img>com si es va registrar com aImageBitmap. El mapa de bits tenyit es desa a la memòria cau com qualsevol ràster vectorial. Una imatge de mapa de bits no es tenyeix mai. - HTML (
renderToHtml,renderToHtmlIndexed). Cada<img>SVG repfilter: url(#pt-ink-…), que apunta a unfeColorMatrixque porta la seva pàgina: un<svg>de mida zero amb el<filter>, col·locat el primer a la pàgina i part deldecorationHtmlde la pàgina a la sortida indexada. Totes les pàgines el porten mentre s'aplica la tinta única, tinguin o no alguna imatge, de manera que un amfitrió que actualitza els blocs un a un no introdueix mai una imatge a la qual falti el filtre.
No es tenyeix mai dues vegades. Fins a postext 1.4 els backends canvas i HTML pintaven les imatges tal qual, així que els amfitrions recolorien el marcatge pel seu compte amb applySingleInkToSvg abans de lliurar-lo. Els adaptadors de paquets i el Sandbox ho continuen fent, perquè la passada sobre el marcatge dona exactament els colors del PDF (consulta l'últim paràgraf d'aquest apartat). Així, una imatge es tenyeix una sola vegada, per tres regles que es compleixen als tres backends:
- El que ja porta la marca es deixa com està. El backend PDF recoloreix
resourceBytesambapplySingleInkToSvg, de manera que uns bytes SVG ja recolorits es dibuixen tal qual. En canvas i en HTML, tampoc no es tenyeix mai una imatge carregada des d'un data URI SVG el marcatge del qual porta la marca. - Desactivat llevat que ho demanis, a postext 1.x. El canvas tenyeix una imatge SVG registrada sense indicació pròpia només quan el renderitzat passa
singleInk: true(RenderPageOptions), i una de registrada ambregisterResourceImage(id, img, { singleInk: true })en qualsevol renderitzat. El backend HTML tenyeix quanrenderToHtmlrepsingleInk: true, o quan el seu resolutorresourceImageUrlportasingleInk: true. Un amfitrió escrit per a la 1.4, que recoloreix el marcatge i registra la imatge descodificada sense indicació, conserva la seva sortida. La pròxima versió major tenyirà per defecte. singleInk: falseno es tenyeix mai. Darrere d'una URL blob o de xarxa no es pot llegir el marcatge, així que una imatge que vas recolorir tu i que descodifiques així es registra ambsingleInk: false, com fanregisterBundleImagesi el Sandbox.bundleImageUrl(bundle)retorna un resolutor que portasingleInk: false, ibundleResourceByteslliura al PDF els bytes del mateix paquet, que el backend PDF recoloreix una vegada.
Els dos backends saben de quin tipus és cada imatge gràcies al VDT: una figura i una imatge de cel·la porten el tipus del seu recurs, i un bloc d'imatge de disseny porta imageKind ('svg' o 'bitmap'), pres del seu recurs durant la composició. En un VDT construït abans que existís imageKind, el canvas tracta com a SVG una imatge de disseny registrada com a font vectorial, i el backend HTML, una amb una URL que és un data URI SVG o que acaba en .svg.
O recoloreixes tu el marcatge o deixes que els backends tenyeixin la imatge original, però no totes dues coses:
import { applySingleInkToSvg, registerResourceImage, renderPage, renderToHtml } from 'postext';
// SVG sense recolorir: es tenyeix mentre diagramStyle.singleInk és actiu…
registerResourceImage('diagrama.svg', imgOriginal, { singleInk: true });
// …o registra'l sense més i demana-ho a cada renderitzat.
registerResourceImage('diagrama.svg', imgOriginal);
const canvas = renderPage(doc.pages[0], doc, { singleInk: true });
// Recolorit abans de descodificar-lo (com fan els amfitrions de la 1.4): es pinta tal qual.
const entintado = applySingleInkToSvg(svgText, tinta);
registerResourceImage('diagrama.svg', await decodificar(entintado), { singleInk: false });
// El backend HTML amb URL al marcatge sense recolorir.
const html = renderToHtml(doc, { resourceImageUrl: urlDe, singleInk: true });renderToHtml pren el valor per defecte del seu singleInk de la indicació del mateix resolutor, així que bundleImageUrl(bundle) no necessita l'opció.
Per a cada color que reescriu la passada sobre el marcatge (valors hexadecimals, rgb() i hsl(), white i black; consulta Com funciona la tinta única), el mapatge per píxels dona el mateix resultat, inclosos les vores suavitzades i els degradats. Tots dos difereixen on la passada sobre el marcatge deixa un color com està: els colors amb nom diferents de white i black, currentColor, les formes i els textos sense emplenament (dibuixats en el negre per defecte) i els mapes de bits incrustats a l'SVG es tenyeixen en pantalla però conserven el seu color al PDF. Dona a cada element del diagrama un color explícit en hexadecimal, rgb() o hsl() per obtenir la mateixa sortida. Quan el canvas no pot tornar a llegir els píxels (un <img> d'un altre origen, carregat sense CORS), la imatge es pinta sense tenyir.
#Estils de paràgraf
La propietat paragraphStyles declara estils amb nom que el document aplica a una sèrie de paràgrafs mitjançant un contenidor :::paragraphs{style="…"} — bibliografies, glossaris, notes, qualsevol bloc d'entrades que vulgui la seva pròpia tipografia, pes, cursiva, mida, interlineat, majúscules, versaletes o un sagnat francès. Tots els camps tipogràfics són opcionals i hereten del text del cos quan no s'indiquen, de manera que un estil només descriu el que difereix del text corrent.
const config: PostextConfig = {
paragraphStyles: [
{
id: 'bibliography',
name: 'Bibliografia',
fontSize: { value: 7, unit: 'pt' },
lineHeight: { value: 1.2, unit: 'em' },
hangingIndent: { value: 2, unit: 'em' },
spaceBetween: { value: 0.25, unit: 'em' },
marginTop: { value: 1, unit: 'em' },
marginBottom: { value: 1, unit: 'em' },
},
],
};## Referències
:::paragraphs{style="bibliography"}
Knuth, D. E. (1984). *The TeXbook*. Addison-Wesley.
Bringhurst, R. (2004). *The Elements of Typographic Style*. Hartley & Marks.
:::| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
id | string | obligatori | Identificador al qual fa referència :::paragraphs{style="…"}. |
name | string | id | Nom llegible, només per a interfícies d'edició. |
fontFamily | string | font del cos | Família tipogràfica. Els seus pesos són fontWeight / boldFontWeight, més avall (els del text del cos si no s'indiquen). |
fontSize | Dimension | mida del cos | Mida de font. |
lineHeight | Dimension | interlineat del cos | Interlineat. em/rem són relatius a la mida del mateix estil, així que un 1.5em heretat s'estreny juntament amb una mida menor. |
color | ColorValue | color del cos | Color del text. Les negretes i cursives conserven els colors d'èmfasi del cos, llevat que boldColor / italicColor fixin els de l'estil. |
textAlign | 'left' | 'justify' | 'center' | 'right' | 'start' | 'end' | alineació del cos | Alineació horitzontal. 'center' i 'right' deixen cada línia en bandera per l'altre costat — una dedicatòria, una signatura. En un paràgraf de dreta a esquerra, 'left' és el seu costat d'inici, la dreta. |
boldColor | ColorValue | bodyText.boldColor | Color de les negretes (una llista d'autors amb els noms en el color de la casa). |
italicColor | ColorValue | bodyText.italicColor | Color de les cursives (…); en un estil italic, dels trams que tornen a rodona. No segueix color: un estil de color les cursives del qual l'hagin de conservar fixa tots dos. |
fontWeight | number | bodyText.fontWeight | Pes del text normal (100–900): una pregunta en seminegreta en una fitxa, un epígraf lleuger. |
boldFontWeight | number | bodyText.boldFontWeight | Pes dels trams en negreta (…). |
italic | boolean | false | Compon els paràgrafs en cursiva: acotacions, un epígraf. Un tram en cursiva … dins seu torna a rodona, com en una cita. |
smallCaps | boolean | false | Compon els paràgrafs en versaletes: les minúscules com a majúscules al 70 % del cos i les majúscules a mida completa, dibuixades igual a tots els backends (vegeu Versaletes): un repartiment, les entrades d'un glossari. |
hyphenation | boolean | partició del cos | Partir mots en justificar (fa servir la llengua del document). |
indent | Dimension | 0 | Sagnat de totes les línies des de la vora esquerra de la columna (o del requadre en què hi ha els paràgrafs); em és el cos del mateix estil. El sagnat de primera línia i el francès es mesuren des d'aquest, així que un vers sagnat pot portar el que no li cap a la línia més endins que el seu propi començament: indent: 1.5em amb hangingIndent: 2.5em posa el vers a 1,5 em i la seva continuació a 4 em. Un valor negatiu compta com a 0. |
firstLineIndent | Dimension | sagnat del cos | Sagnat de la primera línia, des de indent. S'ignora quan hangingIndent no és zero. |
hangingIndent | Dimension | 0 | Sagnat aplicat a totes les línies llevat de la primera, des de indent — la forma clàssica de bibliografies i glossaris. |
spaceBetween | Dimension | 0 | Separació vertical entre paràgrafs consecutius dins del contenidor. Amb 0 les entrades queden enganxades. |
marginTop | Dimension | 0 | Espai sobre el primer paràgraf del contenidor. Col·lapsa amb l'espaiat ja pendent i desapareix a l'inici d'una columna, com qualsevol altre marge. |
marginBottom | Dimension | 0 | Espai mínim sota l'últim paràgraf del contenidor. Com s'uneix amb l'espai del bloc que segueix el contenidor ho decideix bodyText.paragraphContainerSpacing. |
snapToGrid | boolean | true | Torna el flux a la retícula de base sota el contenidor, i l'espai de sota és un mínim. Amb false es conserva l'espai exacte: el text que segueix el contenidor queda fora de la retícula fins al bloc següent que s'hi ajusta (un encapçalament, el final d'una llista, una fórmula en bloc), per a un document que va fora de la retícula o un grup amb un interlineat propi. Dins d'un requadre, que no té retícula, no canvia res. |
textTransform | 'none' | 'uppercase' | 'none' | Caixa dels paràgrafs: 'uppercase' els compon en majúscules (un repartiment, una acotació), també les paraules d'un xip i l'etiqueta d'una :ref. Conserva la longitud lletra a lletra, perquè el mapa de posicions de l'editor continuï sent un a un: una lletra la majúscula de la qual és més llarga (ß) es queda tal qual. Les fórmules no canvien, i un titolet que llegeix el paràgraf com a marca ({firstMark.estil}) pren el text tal com està escrit; el textTransform propi d'un text de disseny el passa a majúscules. |
Una obra de teatre compon les acotacions en cursiva i el repartiment en versaletes:
paragraphStyles: [
{ id: 'acotacion', italic: true, fontSize: { value: 9, unit: 'pt' } },
{ id: 'reparto', smallCaps: true, textAlign: 'center', fontWeight: 600 },
],:::paragraphs{style="acotacion"}
Elsinor. Una esplanada davant del castell. *Francisco* al seu lloc.
:::L'acotació s'imprimeix en cursiva i el nom que conté, en rodona; els pesos, italic i smallCaps d'un estil també s'apliquen dins de les caixes d'avís.
Un llibre de poemes sagna alguns versos i, quan un vers no cap a la mesura, porta el que sobra més endins que el mateix vers. indent desplaça cap endins totes les línies del paràgraf, i el sagnat francès es compta des d'aquí:
paragraphStyles: [
{ id: 'verso', textAlign: 'left', firstLineIndent: { value: 0, unit: 'em' }, hangingIndent: { value: 4, unit: 'em' } },
{ id: 'verso-sangrado', textAlign: 'left', indent: { value: 1.5, unit: 'em' }, hangingIndent: { value: 2.5, unit: 'em' } },
],Un vers en verso-sangrado comença a 1,5 em i la seva continuació a 4 em, a l'altura de les continuacions dels versos en verso. Sense indent, un estil pot sagnar la primera línia o les altres, no totes dues: firstLineIndent s'ignora tan bon punt hi ha hangingIndent.
#El contenidor :::paragraphs
Una línia :::paragraphs{style="<id>"} obre el contenidor i una línia ::: tota sola el tanca; tots els paràgrafs del mig prenen l'estil indicat, mentre que els encapçalaments, les llistes i els altres blocs de l'interior conserven el seu estil habitual. Els contenidors es poden niar dins d'altres contenidors. Un style desconegut no és un error: els paràgrafs es componen com a text de cos normal.
Dins del contenidor el flux abandona la retícula de base — una entrada de 7pt amb interlineat 1.2em no es pot assentar en una retícula de 8pt/1.5em — i l'últim paràgraf torna el flux a la retícula (la retícula mana; l'espai de sota és un mínim, la mateixa convenció que segueixen els encapçalaments). Les entrades es parteixen entre columnes i pàgines com els paràgrafs del cos, amb la mateixa protecció de vídues i òrfenes; un encapçalament immediatament anterior al contenidor es manté unit al seu primer paràgraf.
L'espai sota el contenidor és el més gran entre el spaceBetween i el marginBottom de l'estil i la separació entre paràgrafs del text que l'envolta (una línia quan bodyText.paragraphSpacing està activat), i es fon amb l'espai que el bloc següent deixa a sobre seu, com l'espai entre dos paràgrafs del cos: un encapçalament després d'una bibliografia queda al seu propi marginTop de l'última entrada (o a l'espai de l'estil, si és més gran), i un paràgraf després d'un grup d'entrades més atapeïdes conserva la separació entre paràgrafs del text. El flux torna primer a la retícula sota el text, i el que l'ajust no ha cobert s'arrossega en línies senceres de la retícula, perquè el text que segueix el contenidor hi caigui. Fins a postext 1.4, l'espai de l'estil es posava sota l'última línia abans de l'ajust, l'espai del bloc següent s'hi sumava a sota i la separació entre paràgrafs no comptava; bodyText.paragraphContainerSpacing: 'add' manté aquella regla, i les configuracions desades abans es llegeixen amb ella. Un estil amb snapToGrid: false no s'ajusta: el text que segueix el contenidor en queda a l'espai exacte, fora de la retícula fins al bloc següent que s'ajusti. Un contenidor que es tanca amb una llista es compon com a la 1.4 amb qualsevol de les dues regles: la llista conserva el seu propi espai a sota, i el marginBottom va a continuació d'aquest espai i es fon amb el del bloc següent.
Dins d'un :::callout el contenidor aplica els marges del seu estil de la mateixa manera: marginTop i marginBottom es fonen amb l'espaiat dels blocs que l'envolten (un de negatiu els acosta), i un contenidor que obre el requadre no porta marge superior, igual que a dalt de tot d'una columna. El requadre no té retícula de base on tornar, així que l'espai sota l'últim paràgraf és el més gran de marginBottom, spaceBetween i la separació entre paràgrafs del mateix requadre (el seu body.paragraphSpacing, una línia del seu text; no compta amb paragraphContainerSpacing: 'add'), o el marge superior del bloc següent, si encara és més gran; un marginBottom negatiu, en canvi, acosta el bloc següent. (Fins a postext 1.4, un contenidor dins d'un requadre no aplicava cap dels dos marges.)
const resolved = resolveParagraphStylesConfig(config.paragraphStyles, resolvedBodyText);
// => cada camp no indicat s'omple des del text de cos resolt
const minimal = stripParagraphStylesDefaults(config.paragraphStyles);
// => undefined quan la llista és buida; s'eliminen els marges a zero i `name === id`#Estils de xip
La propietat chipStyles declara els estils amb nom del :chip[texto]{style="…"} en línia: les caixes arrodonides i tintades d'un banc de paraules, una tecla, una etiqueta (la sintaxi i les seves regles de tall de línia són a la referència del format de document). Ve amb un estil per defecte, chip (emplenament blau pàl·lid amb un filet del color principal, cantonades una mica arrodonides i el text com les paraules que l'envolten), així que :chip[…] funciona sense configurar res; declarar chipStyles substitueix aquesta llista. Un xip sense style, o amb un id que cap estil no declara, pren el primer estil.
const config: PostextConfig = {
chipStyles: [
{ id: 'chip', name: 'Banc de paraules' },
{
id: 'key',
name: 'Tecla',
background: { hex: '#fff4d6', model: 'hex' },
borderColor: { hex: '#8a6d1f', model: 'hex' },
borderRadius: { value: 2, unit: 'pt' },
bold: true,
},
],
};Classifica: :chip[pila] :chip[cable] :chip[interruptor]
Prem :chip[Ctrl]{style="key"} + :chip[C]{style="key"}.| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
id | string | obligatori | Identificador que s'usa a :chip[…]{style="…"}. |
name | string | id | Nom llegible, només per a interfícies d'edició. |
backgroundEnabled | boolean | true | Pinta l'emplenament de la caixa. |
background | ColorValue | #e8eef7 | Emplenament de la caixa (vinculable a la paleta). |
borderColor | ColorValue | color principal de la paleta | Color del contorn. |
borderWidth | Dimension | 0.5pt | Gruix del contorn; 0 no en dibuixa cap. Es traça per dins de la vora de la caixa. |
borderRadius | Dimension | 0.3em | Radi de les cantonades, limitat a la meitat de l'alçada de la caixa (un valor gran dona una píndola). |
paddingX | Dimension | 0.3em | Espai entre el contorn i el text, a esquerra i dreta. Forma part de l'avanç del xip. |
paddingY | Dimension | 0.1em | Espai per sobre i per sota de la banda del text. Es pinta fora de la caixa de línia: mai no canvia l'interlineat. |
paddingTop, paddingBottom | Dimension | paddingY | Espai per sobre o per sota de la banda del text, cadascun en lloc de paddingY. La banda va 0,8 em per sobre de la línia de base i 0,25 em per sota, així que el seu centre queda 0,275 em per sobre de la línia de base, més avall que el centre d'una majúscula (uns 0,35 em en la majoria de les fonts): una majúscula o una xifra en un xip rodó (borderRadius: 1em) es veu alta. Un farciment superior més gran que l'inferior en el doble de la diferència la centra: paddingTop: 0.2em amb paddingBottom: 0.05em en una font les majúscules de la qual mesuren 0,7 em. |
fontFamily | string | text que l'envolta | Família del text del xip. Els pesos segueixen el text que l'envolta. |
fontSize | Dimension | text que l'envolta | Cos del text del xip; em és relatiu al text que l'envolta. |
color | ColorValue | text que l'envolta | Color del text del xip. Si no es fixa, les negretes i les cursives conserven els seus colors d'èmfasi. |
bold | boolean | false | Compon el text del xip en negreta, a més de les seves pròpies marques. |
italic | boolean | false | Compon el text del xip en cursiva, a més de les seves pròpies marques. |
gap | Dimension | 0.25em | Espai mínim entre la caixa i la paraula o el xip veí a través d'un espai; un espai més estret es completa dins de l'avanç del xip, així que la justificació mai no se'l menja. No s'hi afegeix res a la vora de la línia ni al costat de la puntuació enganxada. |
Les longituds en em de la caixa (paddingX, paddingY, borderRadius, borderWidth, gap) són relatives al cos del mateix xip. La caixa és una banda de 0,8 em per sobre i 0,25 em per sota de la línia de base, que creix amb paddingY i el contorn; es pinta fora de la caixa de línia i mai no canvia l'interlineat, així que la retícula es manté. Quan la caixa resulta més alta que l'interlineat, un xip pot envair un xip de la línia de sobre o de sota: el sandbox mostra l'avís «Els xips toquen la línia següent» quan dos xips de línies diferents se solapen, amb el solapament en punts, perquè redueixis paddingY, el contorn o fontSize. Un xip alt sense cap xip a sobre ni a sota no s'assenyala.
Al VDT un xip és un segment de línia de kind: 'chip' el camp chip del qual porta els trams de text (cadascun amb la seva cadena de font i la seva amplada), la geometria de la caixa (boxWidth, ascent, descent, paddingX, borderWidth, borderRadius, els marges del gap) i els seus colors; el text del segment és un marcador d'un caràcter, de manera que els desplaçaments de text pla i els mapes de font compten un xip com un caràcter.
const resolved = resolveChipStylesConfig(config.chipStyles);
// => l'estil `chip` integrat si no se'n declara cap; tots els camps plens
const minimal = stripChipStylesDefaults(config.chipStyles);
// => undefined per al valor integrat; s'eliminen els valors per defecte
const style = pickChipStyle(resolved, 'key');
// => l'estil `key`, o el primer#Estils d'avís
La propietat calloutStyles declara els estils de caixa amb nom que el document aplica mitjançant un contenidor :::callout{type="…"} — notes, consells, advertiments, objectius d'aprenentatge, qualsevol contingut separat del text corregut en una caixa tintada o amb vora. Per defecte s'inclou un estil neutre, note (fons gris clar, sense vora, sense franja, sense icona, sense títol), de manera que :::callout funciona sense cap configuració; declarar calloutStyles substitueix aquesta llista per defecte.
const config: PostextConfig = {
calloutStyles: [
{ id: 'note', name: 'Nota' },
{
id: 'objectives',
name: "Objectius d'aprenentatge",
title: 'Objectius',
stripe: { enabled: true, side: 'left' },
icon: { kind: 'glyph', glyph: '✓' },
titleStyle: { textTransform: 'uppercase' },
lists: { bulletChar: '–' },
},
{
id: 'warning',
title: 'Advertiment',
backgroundEnabled: false,
border: { enabled: true, color: { hex: '#AA0000', model: 'hex' }, width: { value: 1, unit: 'pt' } },
borderRadius: { value: 1, unit: 'mm' },
titleStyle: { color: { hex: '#AA0000', model: 'hex' } },
},
],
};:::callout{type="objectives"}
- Descriure les parts del fanal.
- Retallar el ble en fer-se fosc.
:::
:::callout{type="warning" title="No toquis la lent"}
El vidre continua calent una hora després d'apagar la flama.
:::| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
id | string | — | Identificador que selecciona :::callout{type="…"}. Una tanca amb un type desconegut o absent usa el primer estil configurat (el sandbox avisa dels tipus desconeguts). |
name | string | id | Nom llegible (només per a la interfície de l'editor). |
title | string | '' | Text de títol per defecte; buit vol dir sense títol. L'atribut title de la tanca el sobreescriu a cada instància. |
span | 'column' | 'page' | 'side' | 'column' | Extensió horitzontal: la columna, l'amplada completa de l'àrea de contingut o la columna lateral només per a flotants d'una disposició de columna i mitja (layout.sideColumnRole: 'floats') — la caixa surt llavors del flux i s'apila en aquella columna al costat del text que interromp. Es pot sobreescriure per instància amb l'atribut span. En disposicions multicolumna una caixa 'page' es converteix en un bloc d'amplada de pàgina: divideix la pàgina en bandes de columnes i ocupa la seva pròpia columna a tota l'amplada (vegeu la secció del contenidor més avall). |
placement | 'here' | 'auto' | 'top' | 'bottom' | 'fixed' | 'here' | On va la caixa. 'here' la deixa en línia en el flux; 'top' / 'bottom' la fan flotar com un recurs ('auto' pren la primera banda que quedi lliure, la capçalera o el peu) — surt del flux on apareix i ocupa la primera banda lliure en aquell punt o després (el peu de la pàgina actual, o la capçalera / el peu de la pàgina següent que obre el flux), i el text que la segueix omple la pàgina al seu voltant; 'fixed' l'ancora a coordenades de pàgina mitjançant fixed, fora del flux de columnes. Vegeu la secció del contenidor per als detalls. Es pot sobreescriure per instància amb l'atribut placement. |
sideAtColumnEnd | 'before' | 'after' | 'before' | On es col·loca un requadre lateral (span: 'side') quan el text que segueix la seva tanca no continua a la mateixa columna: la columna ja no té lloc per a ell, o les regles de tall el porten més enllà (un paràgraf que les regles de vídues i òrfenes mouen sencer, un encapçalament que va amb el seu text). 'before' el deixa a la seva tanca, en aquella pàgina, al costat del text anterior, pujant des del peu de la columna si no hi cap sota la tanca: el lloc d'una glossa escrita després del passatge que explica. 'after' el posa a l'altura de la primera línia del text que segueix la tanca, a la columna lateral de la pàgina on continua aquest text: el lloc d'un número de línia o d'un titolet marginal escrit abans de la seva línia. Quan el text continua a la mateixa columna, els dos valors posen el requadre a la seva tanca. Un requadre que no té res al darrere en el seu capítol es queda amb el text anterior en els dos casos, i els requadres laterals amb tanca un darrere l'altre conserven el seu ordre. Fins a postext 1.4 tots els requadres laterals es comportaven com 'before', que continua sent el valor per defecte: un estil per a glosses manté els seus requadres a la pàgina del passatge que expliquen. |
fixed | | | Posició d'una caixa 'fixed': un ElementAnchor (to: 'container' = l'àrea de contingut de la pàgina, reflectida a les pàgines parelles; 'page' = la caixa de tall; 'bleed' = la caixa de sang; edge: una de les nou vores del contenidor) més un offset opcional (dimensions x / y). |
floatBarrier | boolean | false | Converteix la caixa en una barrera de flotants: tota figura o taula referenciada abans es col·loca abans que ella — als buits lliures de la pàgina, o en pàgines obertes per davant de la caixa —, de manera que cap flotant no s'escapa més enllà de la caixa que tanca el capítol (normalment un resum de «punts clau»). Les obertures de capítol, els :::part i el final del document són sempre barreres. |
Una caixa span: 'page' en una pàgina a diverses columnes talla la banda sota el text que la precedeix; una figura a tota l'amplada referenciada abans pren aquest tall primer: el text s'anivella, la figura queda just on ha acabat i la caixa continua a sota (o passa a la pàgina següent quan ja no hi cap). Una figura massa alta per seguir el text anivellat obre la pàgina següent, amb la caixa darrere, i la banda que deixa continua acabant anivellada. Una caixa divisible (keepTogether: false) s'obre sota el text i la figura amb els elements que hi caben, i la resta continua a la pàgina següent. | |||
width | 'fill' | 'auto' | 'fill' | 'fill' ocupa tota l'amplada disponible; 'auto' s'ajusta al títol (ús com a etiqueta) i ignora els fills. |
backgroundEnabled / background | boolean / ColorValue | true / #f4f4f4 | Emplenament de la caixa. |
border | { enabled, color, width } | false, #cccccc, 0.5pt | Contorn de la caixa, traçat per dins de la seva vora, com el d'un element de caixa (vegeu Elements de caixa). |
borderRadius | Dimension | 0 | Radi de les cantonades del fons / la vora (limitat a la meitat de l'amplada i de l'alçada de la caixa). La franja el segueix: en una caixa arrodonida es retalla al marc arrodonit, com CSS retalla un border-left a border-radius. La pestanya label conserva les cantonades rectes. |
padding | { top, right, bottom, left } | 0.75em cadascun | Marge interior entre la vora de la caixa i el seu contingut. Els valors en em són relatius a la mida del cos de l'avís. |
stripe | { enabled, side, width, color } | false, 'left', 1.5em, color principal | Franja sòlida al llarg d'un costat. Una franja 'left' / 'right' estreny el contingut; una franja 'top' el desplaça cap avall. 'left' i 'right' són costats del flux del text (en un llibre de dreta a esquerra 'left' és la dreta del plec); 'start' i 'end' segueixen la direcció de la caixa mateixa, de manera que un :::callout{dir=ltr} en un llibre àrab posa a la seva esquerra una franja 'start'. En una caixa amb borderRadius les seves cantonades exteriors s'arrodoneixen amb les del marc (fins a postext 1.4 quedaven rectes i sobresortien per fora de l'arrodoniment). |
icon | { kind, glyph, resourceId, fontFamily, fontWeight, size, width, color, align, position, cornerSide } | 'none', font d'encapçalaments, 400, 1.5em, color principal, 'top' | Un glif de text (kind: 'glyph') o un recurs bitmap / SVG (kind: 'resource' + resourceId) al costat del contingut. Amb franja lateral la icona se centra sobre la franja; si no, reserva la seva pròpia columna (size + titleStyle.gap). align: 'center' la centra verticalment sobre el contingut. Una imatge de recurs s'ajusta dins del quadrat conservant la seva proporció; una icona més alta que el contingut fa més gran la caixa per contenir-la (i, amb align: 'center', centra el contingut sobre ella). Amb width la imatge s'ajusta a una caixa de width × size (una tira ampla d'icones). position: 'corner' penja la icona d'una cantonada superior com una insígnia, mig fora de la vora i sense ocupar lloc en el contingut; una icona ampla (width) se centra a la cantonada segons l'amplada amb què es dibuixa, i a la cantonada esquerra el títol comença passada la seva meitat interior (fins a postext 1.4 es col·locava segons la seva alçada, de manera que una tira ampla sobresortia de la caixa i tapava el títol); cornerSide tria la cantonada: 'right' / 'left', o 'outer' / 'inner', que segueixen la paritat de la pàgina amb marges simètrics (exterior = dreta en una pàgina senar, esquerra en una parella). |
marker | { kind, glyph, resourceId, fontFamily, fontWeight, size, color, align, gap, rule } | 'none', font de títols, 400, 1.5em, color principal, 'center', 0.5em, filet desactivat (0.5pt, color principal, longitud 0) | Una segona icona dibuixada fora de la caixa, en una columna a la seva esquerra, amb un rule (filet vertical) opcional entre ella i la caixa: la mà de «toca aquí» al costat d'un distintiu d'autoavaluació. El marc passa a ser [marker][rule][gap][box] i mesura el que fa el més alt dels tres; align els centra entre si o els alinea a dalt. rule.length és un mínim: el filet abasta sempre almenys l'alçada de la caixa. |
titleStyle | { fontFamily, fontSize, fontWeight, italic, color, textTransform, gap, letterSpacing, indent, lineHeight } | font d'encapçalaments, mida del cos, 700, false, color principal, 'none', 0.5em, 0, 0, 1.2em | Tipografia del títol. gap és l'espai entre el títol i el primer fill (i el buit de la columna de la icona). lineHeight és l'interlineat de les línies del títol, i em compta la mida del mateix títol; la línia de base queda a 0,8 d'aquest dins de cada línia, com en el text corregut, així que un títol amb l'interlineat del text (lineHeight: 12pt sobre una retícula de 12 pt) deixa el requadre en un nombre enter de línies i el seu títol a la retícula, mentre que l'1,2 em per defecte afegeix una fracció de línia a cada requadre. textTransform: 'uppercase' conserva la longitud del text. letterSpacing aplica un tracking al títol (canvas letterSpacing / PDF Tc); indent el desplaça a la dreta de la vora interior de la caixa. Una insígnia de cantonada que penja del costat del títol (la cantonada esquerra) reserva abans el seu propi lloc — la seva meitat interior més gap — de manera que el títol l'esquivi caigui a la pàgina que caigui; indent només hi afegeix a partir d'aquí. |
body | { fontFamily, fontSize, lineHeight, color, boldColor, italicColor, fontWeight, boldFontWeight, italic, smallCaps, textAlign, hyphenation, paragraphSpacing, firstLineIndent } | hereta de bodyText; italic / smallCaps false | Tipografia dels paràgrafs i elements de llista dins de la caixa. Cada camp hereta del text de cos quan no s'indica; italicColor fixa el color de les cursives (una cita destacada en cursiva amb el color de la caixa). fontWeight / boldFontWeight fixen el pes dels trams normals i en negreta; italic compon la caixa en cursiva, amb els trams … en rodona; smallCaps, en versaletes. Vegeu Tipografia dins d'un requadre per a la resta del que pren el text de la caixa. |
lists | { bulletChar, color, indent, gap, itemSpacing, bulletFontSize, bulletFontWeight } | hereta de unorderedLists | Tipografia de les llistes dins de la caixa (color, indent, gap i itemSpacing s'apliquen també a les llistes ordenades). bulletFontSize / bulletFontWeight componen la vinyeta amb la font del cos de la caixa a aquella mida i aquell pes (una vinyeta gruixuda de color). |
label | { fontFamily, fontSize, fontWeight, color, background, position, height, paddingX, offset, inset, icon, rule } | sense definir (sense pestanya) | Una pestanya sobre la vora superior de la caixa que imprimeix l'atribut label de la tanca —el número d'un requadre numerat («REQUADRE 1-1»)—. S'enganxa a la cantonada position ('top-right' / 'top-left') amb una separació inset, sobresurt offset per sobre de la vora (aquest espai forma part del bloc, a més de marginTop, i es conserva també al capdamunt d'una columna), fa height d'alçada amb paddingX a cada costat del text, i pot portar un recurs icon al costat ({ resourceId, width, gap }, cap a l'interior) i un filet rule ({ enabled, color, width }) al llarg de la vora superior des de la cantonada oposada fins a ella. Per defecte: font d'encapçalaments, mida del cos, 700, blanc sobre el color principal, 1.4em d'alçada, 0.6em de farciment. La pestanya és una forma pròpia recolzada al marc, així que conserva les cantonades rectes sigui quin sigui el borderRadius de la caixa. |
columnGap | Dimension | 1.5em | Separació entre les columnes d'un grup :::columns dins de la caixa (vegeu la secció del contenidor). |
marginTop / marginBottom | Dimension | 0.75em / 0.75em | Espai sobre la caixa (es fon amb el marge del bloc anterior) i espai mínim sota seu (l'espai exacte amb snapToGrid: false). Una caixa flotant (placement: 'top', 'bottom' o 'auto') deixa entre la seva banda i el text el buit dels flotants, una línia del cos; un marginBottom més gran fixa l'espai sota una caixa en una banda superior, i un marginTop més gran l'espai sobre una caixa en una banda inferior, arrodonits a la retícula amb la banda. Fins a postext 1.4 una caixa flotant no feia cas dels seus marges. |
snapToGrid | boolean | true | Amb true el flux torna a la retícula de base després de la caixa, de manera que l'espai sota seu és marginBottom arrodonit cap amunt a línies senceres de la retícula. Amb false la caixa conserva el seu marginBottom exacte, que es fon amb el marge superior del bloc següent — dues caixes consecutives d'aquest estil queden exactament a max(marginBottom, marginTop) — i el text que la segueix pot quedar fora de la retícula fins al punt d'ajust següent (un encapçalament, el final d'una llista), com després d'un encapçalament amb headings.snapToGrid: false. Pensat per a documents fets de caixes apilades (fitxes, formularis). S'aplica a les caixes del flux; les caixes d'amplada de pàgina en una maquetació de diverses columnes, les flotants, les fixes i les laterals conserven la retícula, perquè les bandes de columnes i les zones de flotants s'hi disposen a sobre. Les palanques d'equilibri de columnes no canvien: una caixa que tanca una columna continua baixant fins a l'última línia de la retícula d'aquella columna. |
keepTogether | boolean | true | Amb true la caixa es manté sencera: un avís que no cap a l'espai restant passa sencer a la columna o pàgina següent. Només una caixa més alta que una columna completa i buida — una pàgina sencera en una caixa span: 'page' — no es pot mantenir sencera: es parteix amb les regles de false que segueixen en lloc de desbordar, començant on apareix, i una continuació que cap en una columna passa llavors sencera; una caixa flotant (placement: 'top' | 'bottom' | 'auto') tan alta no flota, sinó que continua en el flux on apareix. Amb false qualsevol caixa es pot partir entre els seus blocs fills o entre les línies d'un paràgraf o d'un element de llista: el tall més profund que hi cap tanca la columna actual (o, en una caixa span: 'page', la pàgina, arran del peu de les columnes) i la resta continua a l'inici de la següent en una caixa pròpia — mateix marc i franja, sense icona, i sense títol tret que repeatTitle el repeteixi —, partint-se de nou si continua sent massa alta. El text de cada fragment conserva, buida, la columna que ocupa al començament una icona en línia, així que la caixa té la mateixa mesura a totes les pàgines. Un tall dins d'un element de llista deixa la vinyeta amb el començament. El marc de cada fragment comparteix el contentIndex / containerId de la tanca i registra callout.part / callout.continued. Fes-lo servir en una caixa de «punts clau» llarga de tancament juntament amb headings.balancing.beforeSpan, o en un estil de nota les caixes del qual no hagin d'expulsar mai una figura de la pàgina. Una caixa niada (un :::callout dins d'un altre) és un fill més de la seva mare: un tall pot caure abans o després d'ella, i a dins només si el seu propi estil permet partir-la (keepTogether: false, o més alta que una columna completa), segons el seu propi splitMinLines. |
splitMinLines | number | 2 | Mínim de línies de text que un fragment d'una caixa partida (keepTogether: false, o una caixa unida més alta que una columna completa) conserva a cada costat del tall. Només protegeix el text: un costat amb almenys una figura, taula, fórmula en bloc o caixa niada és vàlid tingui les línies que tingui, de manera que una caixa d'imatges pot deixar-ne una sola en una pàgina. Un tall dins d'un paràgraf o d'un element de llista continua comptant totes les línies de cada costat (una figura o fórmula, com una línia), i a més deixa almenys layout.boxChildSplitMinLines línies d'aquell paràgraf o element a cada costat (dues per defecte; aquest mínim si és menor, així que amb 1 n'hi ha prou amb una). Amb els valors per defecte, un element de dues o tres línies no es parteix mai i un de quatre només es parteix en dues i dues. Amb el valor per defecte cap caixa no es parteix deixant una línia de text sola al peu d'una columna o a l'inici de la següent; si cap tall no compleix el mínim, la caixa passa sencera. (Abans de postext 1.5, un tall dins d'un paràgraf només comprovava les línies de tot el costat, així que un element de dues línies es podia partir en una i una si altres línies de la caixa completaven el mínim.) |
repeatTitle | boolean | false | Repeteix el títol al principi de cada continuació d'una caixa partida, seguit de continuedSuffix («Punts clau (cont.)»). La repetició pren l'estil del títol. Una caixa sense títol no repeteix res. Vegeu Marques d'un requadre partit. |
continuedSuffix | string | '(cont.)' | Text després del títol repetit, en la llengua del document (locale o, si no, la de la partició de mots), com el d'una taula partida, i unit al títol igual que en aquella. |
continuesMarkerEnabled | boolean | false | Posa continuesMarker sota l'última línia de cada part d'una caixa partida que continua, dins de la caixa. |
continuesMarker | string | 'Continued' / 'Continúa' | Text d'aquest indicador (el «(CONTINUA)» d'un guió), amb la font i el cos del text de la caixa, segons la llengua del document. |
continuesMarkerAlign | 'left' | 'center' | 'right' | 'right' | Posició de l'indicador en l'amplada interior de la caixa. |
continuesMarkerItalic | boolean | true | Compon l'indicador en cursiva. |
#El contenidor :::callout
Una línia :::callout{type="<id>"} obre la caixa i una línia ::: tota sola la tanca. La tanca admet quatre atributs — type (l'id de l'estil), title (sobreescriu el títol de l'estil), span i placement (sobreescriuen els valors de l'estil) — i el contingut intermedi es compon dins la caixa: un títol opcional i després els paràgrafs, llistes, cites, fórmules o recursos incrustats, cadascun amb la tipografia body / lists de l'estil (els encapçalaments conserven els seus estils habituals). Els marges entre fills es fonen com en el text corregut; l'interior abandona la retícula de base i el flux hi torna després de la caixa garantint com a mínim marginBottom per sota (la retícula mana; el marge és un mínim — la mateixa convenció que segueixen els recursos). Un estil amb snapToGrid: false conserva, en canvi, el marginBottom exacte, i el text després de la caixa queda fora de la retícula fins al següent encapçalament o final de llista. Un encapçalament immediatament anterior a un avís es manté unit a ell.
Límits d'aquesta versió:
- Un avís es manté sencer tret que el seu estil indiqui
keepTogether: false. Quan no cap a l'espai que queda de la columna passa sencer a la columna o pàgina següent — també des d'una columna buida que les bandes de flotants o un topall de banda han escurçat, sempre que una columna completa l'allotgés. Una caixa més alta que una columna completa es parteix al seu lloc, com una de divisible; només la que cap tall no pot partir (una figura, taula o grup:::columnsmés alt que la columna, o unsplitMinLinesque cap tall no compleix) es col·loca igualment i desborda; la maquetació registra llavors un avíscalloutOverflow(VDTDocument.warnings), que el sandbox llista. Una caixa divisible deixa enrere la part que hi cap — fills sencers, o les línies d'un paràgraf fins asplitMinLinesa cada costat (n'hi ha prou amb una figura, taula o fórmula en bloc en un costat) — i continua en una caixa sense icona a la columna o pàgina següent, sense títol tret que l'estil el repeteixi i, si es vol, amb un indicador sota la part que deixa (vegeu Marques d'un requadre partit). - Els flotants cedeixen davant d'una caixa indivisible. Quan el bloc que segueix la referència d'una figura és un avís
keepTogether, no s'agafa un buit que deixaria la caixa sense cap columna de la banda actual on anar a parar (la columna de la referència o una de buida posterior, que abans del flotant l'allotjava): la figura passa al seu buit següent, normalment la pàgina següent, i la caixa segueix en el flux — com la compondria un caixista, en lloc d'expulsar la caixa de la pàgina i deixar la columna amb la figura sola. span: 'page'en una disposició multicolumna converteix la caixa en un bloc d'amplada de pàgina: es compon a l'amplada completa de l'àrea de contingut i divideix la pàgina en bandes de columnes — les columnes de text que hi ha a sobre es tanquen a la línia de tall, la caixa ocupa la seva pròpia columna a tota l'amplada i a sota s'obre una banda nova de columnes de text, de manera que el flux continua sota la caixa a totes les columnes. On les columnes estan anivellades — al començament d'una pàgina, just sota un encapçalament d'obertura ambspan: 'page', just sota un altre bloc d'amplada de pàgina o just sota una banda de flotants superior — la caixa simplement talla allà. Si arriba a mitja pàgina, amb les columnes desiguals, es compon com ho faria un caixista: el text que hi ha a sobre es talla anivellat a totes les columnes (el motor repeteix la col·locació amb les columnes de la banda escurçades al mateix nombre de línies de retícula, de manera que el text desborda de columna en columna de forma natural i continuen aplicant-se totes les regles d'òrfenes, vídues i encapçalaments units al seu text), la caixa ocupa l'amplada de la pàgina i les columnes continuen a sota. El tall costa un parell de passades de col·locació addicionals; quan la línia de tall no deixaria lloc per a la caixa més el mínim de línies vídues de cos per sota, o cap disposició no encaixa després d'uns quants intents, la caixa passa al començament de la pàgina següent. El text de sobre respecta el tall: un paràgraf que no pot començar a les poques línies que li queden a una columna sota una figura passa a la columna següent (la figura queda sola a la seva columna), i quan un bloc encara passaria del tall, el tall es baixa una línia en lloc de quedar-se on acaba aquell bloc. Una caixa partida que obre la banda també es talla allà, de manera que la seva resta mai no passa del tall. Ambheadings.balancing.beforeSpan(el valor per defecte) la banda que deixa es talla anivellada després d'ella — com la banda de tancament d'un capítol — i, quan l'estil permet partir-la (keepTogether: false), la part de la caixa que cap sota les columnes anivellades tanca la pàgina i la resta obre la següent; ambbeforeSpan: falsela pàgina que deixa simplement s'equilibra com sempre, sense forçar un salt de pàgina. Un encapçalament just abans d'un bloc d'amplada de pàgina no viatja amb ell. En disposicions d'una columnaspan: 'page'és simplement en línia.placement: 'fixed'treu la caixa del flux: es compon (width: 'auto's'ajusta al títol;'fill'pren l'amplada de la columna de text sota el punt d'ancoratge) i es fixa a la pàgina on apareix en el flux, a la posició que descriuenfixed.anchor/fixed.offset— per defecte la cantonada inferior esquerra de l'àrea de contingut. Les columnes de text que cobreix cedeixen aquella zona (retallada per baix, o per dalt quan la columna encara és buida), exactament com una banda de flotants; quan la zona ja conté text, un flotant o un bloc d'amplada de pàgina, la caixa passa a la pàgina següent. Una caixa fixa que tanca el capítol (el bloc següent és una obertura de capítol, un:::part, una caixa barrera de flotants o el final del document) anivella primer les columnes que queden a sobre (headings.balancing.trailing), de manera que una pàgina de tancament curta acaba anivellada amb el distintiu a sota. La caixa i els seus fills viuen apage.floatsi es pinten fora del retall de columna en tots els backends.placement: 'top' | 'bottom'fa flotar la caixa com un recurs: surt del flux on apareix i pren la primera banda lliure a partir d'aquest punt — el peu de la pàgina actual ('bottom'), o el cap / el peu de la pàgina següent que obre el flux — a l'amplada de la columna (span: 'column') o a l'amplada completa de l'àrea de contingut (span: 'page'); el text que la segueix omple la pàgina que ha deixat. El seu marc i els seus fills van apage.floats, com els d'una caixa fixa. Una caixa flotant que encapçala una pàgina nova es col·loca abans que les figures que esperen aquella pàgina, i una figura citada en una pàgina anterior que llavors hi cap a sota pren la resta d'aquella pàgina encara que quedessin menys de tres línies de text (una pàgina galeria: caixa més figura, sense text entre totes dues). Una caixaspan: 'side'mai no flota: s'apila al costat del text sigui quin sigui el seuplacement.width: 'auto's'ajusta només al títol; els fills s'ignoren.- Un
:::calloutniat dins d'un altre avís és una caixa pròpia: es compon amb el seu propi estil (fons, vora, radi, farciment, franja, títol, icona, marcador, pestanya, tipografia) a l'amplada interior completa de la seva caixa mare i s'apila com un fill més, i el seumarginTop/marginBottomes fon amb el dels veïns. El seuspani el seuplacement(de la tanca o de l'estil) s'ignoren —una caixa niada sempre flueix dins la seva mare—, igual quefloatBarrierisnapToGrid. Les caixes es nien a qualsevol profunditat i poden anar dins d'un grup:::columns(cadascuna sencera, en una columna). Quan la caixa mare es parteix, el tall cau abans o després d'una caixa niada, o dins d'ella si el seu estil permet partir-la; cada fragment torna a dibuixar els marcs que travessa el tall, i una caixa niada que continua del fragment anterior perd el títol i la icona, com una continuació de primer nivell. - Un grup
:::columns{count=N}…:::entre els fills compon aquests fills enNcolumnes d'igual amplada, separades percolumnGap, dins la caixa: la seqüència es talla pels límits de bloc o de línia que millor anivellen les columnes (un paràgraf o element de llista tallat a mitges continua al cap de la columna següent sense la vinyeta), cada columna comença a la part superior del grup i el grup fa l'alçada de la columna més alta; els fills posteriors recuperen l'amplada completa. Una caixa partible (keepTogether: false) mai no talla dins d'un grup. Serveix per a un resum de punts clau a dues columnes o per a les taules d'un requadre ample posades una al costat de l'altra. - El cinquè atribut de la tanca,
label, s'imprimeix a la pestanyalabelde l'estil (vegeu més amunt)::::callout{type="recuadro" label="RECUADRO 1-1" title="La regla del octeto"}; sense estil de pestanya l'atribut s'ignora.
Al VDT la caixa és un bloc marc de type: 'callout' la decoració del qual (fons, franja, icona, títol) viu a designOverlay, seguit dels seus blocs fills a la mateixa columna; el marc i cada fill porten el containerId de la tanca. Una caixa niada és un marc type: 'callout' propi entre els fills, seguit dels seus blocs; conserven el containerId de la tanca de primer nivell (la col·locació i l'equilibrat continuen veient una sola unitat) i afegeixen calloutPath, els identificadors de contenidor de les tanques niades que els envolten, de la més externa a la més interna (el d'un marc niat és l'última entrada). El PDF etiquetat dona a cada caixa niada un Div dins del de la seva mare. Les imatges d'icona es resolen com les dels recursos: el registre d'imatges del canvas, l'opció resourceImageUrl de l'HTML i el proveïdor resourceBytes del PDF.
const resolved = resolveCalloutStylesConfig(config.calloutStyles, resolvedBodyText, resolvedHeadings, resolvedUnorderedLists, config.locale);
// => cada camp heretat s'omple des de les seccions resoltes; el locale
// opcional tria la llengua de les cadenes de continuació
const minimal = stripCalloutStylesDefaults(config.calloutStyles);
// => undefined per al `note` per defecte; s'eliminen els valors estàtics per defecte#Tipografia dins d'un requadre
Una caixa d'avís —un requadre, com l'anomenen el tauler Estils de requadre del sandbox i el format del document— compon el seu contingut amb la tipografia body i lists del seu estil; tota la resta conserva els estils del document:
- Els paràgrafs prenen el
bodyde la caixa: font, cos, interlineat, color, colors d'èmfasi, gruixos,italic,smallCaps, alineació, partició de mots, sagnat i espaiat entre paràgrafs. Un camp sense definir hereta debodyText. Un color d'èmfasi heretat conserva el seu vincle amb la paleta, així que les negretes, les cursives i les etiquetes de:refd'una caixa canvien ambcolorPaletteigual que fora d'ella. (Fins a postext 1.4 la negreta d'una caixa es quedava en#295AA3fos quin fos el color principal). - Les llistes de vinyetes prenen el
listsde la caixa (caràcter de vinyeta, color, mida i gruix del glif, sagnat, separació, espaiat entre elements) sobreunorderedLists, i el seu text és el del cos de la caixa. Unlists.bulletCharo unlists.colordiferent del del document (unorderedLists) substitueix la vinyeta o el color de tots els nivells; un que el repeteix, o que no es defineix, deixa a cada nivell els seus (unorderedLists.levels), de manera que els guions dels nivells niats es conserven a la caixa. - Les llistes ordenades prenen
lists.indent,gapiitemSpacing, ilists.colorsempre que l'estil el defineix, també quan coincideix amb el color de vinyeta del document; un estil que no el defineix deixa els números enorderedLists.color. (Fins a postext 1.4 el color només arribava als números si era diferent d'unorderedLists.color, així que donar-li justament aquest color no tenia efecte). El número —la seva font, el seu cos i el seu separador— ve delsorderedListsglobals, perquèlistsnomés té camps de vinyeta: és allà on es dona estil als números d'una caixa. - Els
:::paragraphsdins d'una caixa fan servir el seu estil de paràgraf, també en caixes niades. Els camps que l'estil no defineix hereten delbodyTextdel document, no delbodyde la caixa (ni de la seva cursiva ni de les seves versaletes). - Els grups
:::columnsno tenen estil propi: totes les columnes comparteixen la tipografia de cos i de llistes de la caixa, icolumnGapfixa la separació entre elles. - Les cites prenen la font, el cos i els gruixos del text de la caixa (en cursiva i en gris, com en el text corregut) i les seves versaletes. Els encapçalaments conserven els seus estils; les fórmules en bloc, la configuració de matemàtiques.
- Les figures i les taules conserven els estils de peu i de taula del document, a l'amplada interior de la caixa; els seus gruixos normal i negreta segueixen els gruixos del
bodyde la caixa. - Els xips conserven el seu estil de xip; una mida en
emes mesura sobre el cos del text de la caixa. :::spacees mesura en línies del text de la caixa (vegeu:::spaceper saber on es descarta).- Una caixa niada pren el seu propi estil complet; el seu
span, el seuplacement, el seufloatBarrieri el seusnapToGrids'ignoren.
#Marques d'un requadre partit
Quan una caixa es parteix entre columnes o pàgines (keepTogether: false, o una caixa més alta que una columna), cada part posterior a la primera comença sense el títol ni la icona i, per defecte, res no avisa el lector que la caixa continua. Dues opcions afegeixen les marques que fan servir un llibre o un guió:
repeatTitle: truerepeteix el títol al començament de cada continuació, seguit decontinuedSuffix— «Punts clau (cont.)» per defecte. La repetició pren l'estil del títol, així que ambtextTransform: 'uppercase'dona el «HAMLET (CONT.)» d'un guió. La icona i la pestanya d'etiqueta es queden a la primera part.continuesMarkerEnabled: trueposacontinuesMarker— «Continúa» en un document en castellà, «Continued» en anglès — sota l'última línia de cada part que continua, dins la caixa, en la font i el cos del text de la caixa: en cursiva tret quecontinuesMarkerItalicsiguifalse, i alineat a la dreta tret quecontinuesMarkerAligndigui'left'o'center'. L'indicador ocupa lloc a la part que tanca, i el tall es tria perquè hi càpiga.
calloutStyles: [{
id: 'parlamento',
keepTogether: false,
titleStyle: { textTransform: 'uppercase' },
repeatTitle: true,
continuedSuffix: '(CONT.)',
continuesMarkerEnabled: true,
continuesMarker: '(SEGUEIX)',
continuesMarkerAlign: 'center',
continuesMarkerItalic: false,
}],:::callout{type="parlamento" title="Hamlet"}
Un parlament prou llarg per passar del peu de la pàgina…
:::La part que tanca la pàgina acaba amb «(SEGUEIX)» i la pàgina següent comença amb «HAMLET (CONT.)». Totes dues són elements de paginació: en un PDF accessible són artefactes i a l'HTML s'amaguen a les tecnologies de suport, de manera que el títol es llegeix una sola vegada. Al VDT són blocs de text de designOverlay marcats amb artifact: true.
#Parts
La propietat parts configura les pàgines separadores de part que un document obre amb un contenidor :::part{number="…" title="…"} — la pàgina «Part I — Fonaments» que agrupa una sèrie de capítols. Una part ocupa sempre una pàgina pròpia: el contenidor salta a una pàgina nova amb la paritat configurada, la converteix en una pàgina d'una sola columna l'àrea de cos de la qual surt de parts.margins, compon el disseny d'obertura sobre la pàgina sencera i torna a saltar després de la tanca final perquè el capítol següent (amb el seu propi breakBefore.parity) comenci net — amb els valors per defecte d'H1 això produeix la seqüència clàssica: pàgina de part en recto, verso en blanc, capítol al recto següent.
const config: PostextConfig = {
parts: {
breakBefore: { parity: 'odd' },
breakAfter: { enabled: true, parity: 'any' },
margins: { top: { value: 9, unit: 'cm' }, left: { value: 3, unit: 'cm' }, right: { value: 3, unit: 'cm' } },
design: {
elements: [
{
kind: 'text', id: 'number', content: 'Part {numberRoman}',
fontSize: { value: 12, unit: 'pt' }, fontWeight: 600, align: 'left',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: { value: 3, unit: 'cm' }, y: { value: 5, unit: 'cm' } }, size: { width: 'auto', height: 'auto' } },
},
{
kind: 'text', id: 'title', content: '{titleText}',
fontSize: { value: 28, unit: 'pt' }, fontWeight: 700, align: 'left', overflow: 'wrap',
placement: { anchor: { to: '#number', edge: 'below' }, size: { width: { value: 15, unit: 'cm' }, height: 'auto' } },
},
],
},
bodyStyle: { fontSize: { value: 11, unit: 'pt' }, numberColor: { hex: '#AA0000', model: 'hex' } },
},
};:::part{number="I" title="Fonaments"}
1. El fanal i les seves parts
2. Retallar el ble
3. Llegir el temps
:::
# El fanal i les seves parts| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
page | boolean | true | Si un :::part obre una portadella. Amb false no s'obre cap pàgina ni es compon el cos de la tanca: el número, el títol i la paleta de la part regeixen a partir del contingut següent, sense salt propi. Ús típic: htmlViewer.overrides.parts.page: false, una edició en pantalla sense portadelles. |
breakBefore.parity | HeadingBreakParity | 'odd' | Paritat de la pàgina on s'obre la part. Mateixos valors i mateixes regles de pertinença de les pàgines en blanc que el breakBefore dels encapçalaments: un blanc inserit per assolir la paritat pertany a la part (el seu ja resol a la part nova); el separador obligatori de 'always-*' pertany al contingut anterior. |
breakAfter.enabled | boolean | true | Passa el contingut posterior a la tanca final a una pàgina nova. Amb false continua a la columna única de la pàgina de part. |
breakAfter.parity | HeadingBreakParity | 'any' | Paritat d'aquesta pàgina nova. Deixa-ho en 'any' i deixa que el mateix breakBefore.parity del capítol següent decideixi si segueix un verso en blanc. El salt s'aplica en col·locar el bloc següent, així que una part que tanca el document no deixa una pàgina buida al final. |
margins | PageMargins | marges de pàgina | Àrea de cos de la pàgina de part — la columna única on flueixen els blocs de dins la tanca. Cada costat hereta el marge de pàgina quan no es defineix; mirror intercanvia interior/exterior a les pàgines parelles exactament com els marges de pàgina. |
design | DesignSlot | buit | Disseny d'obertura. El seu contenidor és la caixa de tall de la pàgina, així que els ancoratges al contenidor i a 'page' coincideixen, i 'bleed' arriba fins a la sang quan les marques de tall són actives. Purament decoratiu: mai no reserva espai de cos — puja margins.top perquè el cos no el trepitgi. Quan és buit se sintetitza un text per defecte amb la tipografia de l'H1 a la cantonada superior esquerra de l'àrea de cos, amb el numberSeparator de l'H1 entre número i títol. |
versoDesign | DesignSlot | buit | Disseny del verso en blanc que segueix la pàgina de part (el revers del full separador): mateix contenidor i marcadors que design. Deixa'l buit per a un verso llis. Només es pinta quan la pàgina que segueix la de part queda en blanc, cosa que requereix un salt amb paritat: consulta El disseny del verso més avall. |
bodyStyle.fontFamily, fontSize, lineHeight, color, textAlign | com a bodyText | hereten de bodyText | Tipografia dels paràgrafs, cites i elements de llista de dins la tanca. Els gruixos, els colors d'èmfasi i la partició de mots vénen del text de cos. |
bodyStyle.bulletColor | ColorValue | unorderedLists.color | Color de les vinyetes de les llistes no ordenades de dins la part. |
bodyStyle.numberColor | ColorValue | orderedLists.color | Color dels números de les llistes ordenades de dins la part. Els números van sempre en el gruix de negreta del cos, perquè una llista de capítols es llegeixi com un índex. |
bodyStyle.unorderedLists | UnorderedListsConfig | — | Sobreescriptures parcials aplicades sobre les unorderedLists del document dins la part, després de bulletColor. Els valors generals es propaguen als nivells que els heretaven; les entrades de levels s'apliquen només al seu nivell. |
bodyStyle.orderedLists | OrderedListsConfig | — | Sobreescriptures parcials aplicades sobre les orderedLists del document dins la part, després de numberColor i el gruix de negreta — p. ex. un separator '•' amb la seva pròpia separatorFontFamily i separatorColor per a la llista de capítols d'una obertura de part. |
#Marcadors del disseny de part
El disseny resol el conjunt de marcadors d'encapçalament amb els valors propis de la part: {titleText} és el title de la tanca; {number} el number tal com s'ha escrit; {numberDecimal}, {numberRoman}, {numberRomanLower}, {numberAlpha}, {numberAlphaLower} el reformaten — el número s'interpreta com a decimal, com a numeral romà o com a numerals xinesos, amb les paraules que els envolten o sense ("IV", "iv", "4", "4", "四", "卷四" i "第四卷" donen {numberDecimal} = 4) i resolen a '' per a qualsevol altra cosa. També hi ha disponibles {partTitle} / {partNumber}, {chapterTitle} / {chapterNumber} (el capítol anterior a la part), {pageNumber}, {totalPages}, {bookTotalPages} i els marcadors de metadades. {attr.<key>} llegeix els atributs de l'H1 del capítol actual.
#El disseny del verso
versoDesign decora la pàgina que segueix la de part quan aquesta pàgina no té contingut: el revers del full separador. La part tota sola mai no deixa aquesta pàgina en blanc. breakAfter.parity val 'any' per defecte, així que el contingut després de la tanca comença a la pàgina següent tret que alguna cosa demani una paritat:
- El títol del capítol següent. El
breakBeforeper defecte de l'H1 és'always-odd', i'odd'fa el mateix després d'una pàgina de part en recto: el capítol passa al recto següent i el verso queda en blanc. El disseny del verso es pinta. parts.breakAfter: { enabled: true, parity: 'odd' }. La mateixa part demana el recto següent, faci el que faci el títol següent. Fes-lo servir quan els capítols puguin obrir en qualsevol cara (breakBefore.parity: 'any', obreakBefore.enabled: false).
Sense cap dels dos, el capítol obre al verso i no es dibuixa disseny de verso. Amb breakAfter.enabled: false el contingut continua a la mateixa pàgina de part. El verso pren la paleta de la part, així que un palette="band=#…" a la tanca també el recoloreja.
parts: {
breakBefore: { parity: 'odd' },
breakAfter: { enabled: true, parity: 'odd' }, // sempre queda un verso en blanc per pintar
versoDesign: {
elements: [{
kind: 'box', id: 'campo',
style: { backgroundColor: { hex: '#b07d2b', model: 'hex', paletteId: 'band' } },
placement: { anchor: { to: 'page', edge: 'top-left' }, size: { width: 'fill', height: 'fill' } },
}],
},
}Una part que tanca el seu capítol. En un llibre maquetat capítol a capítol (el Sandbox, buildBundle), una tanca :::part pot ser un capítol tota sola, o el final d'un. La seva pàgina de part és llavors l'última del capítol, i el capítol següent assumeix el que la part encara deu: aplica breakAfter abans del seu primer bloc i pinta versoDesign a la seva primera pàgina quan aquesta queda en blanc. Les pàgines surten com sortirien amb el llibre sencer en un sol document. Després de la tanca només hi poden venir directives que no col·loquen res (:::numbering, :::space); qualsevol altra cosa és contingut del capítol, que llavors fa el salt per si mateix. Un capítol buit just després de la part és una pàgina pròpia: aquesta pàgina és el verso. Un amfitrió que maqueta els capítols pel seu compte ho obté de continuationAfter(), que retorna afterPartPage: true per a un capítol que acaba amb una part; passa-ho a la continuation del capítol següent.
#El contenidor :::part
Una línia :::part{number="…" title="…"} obre la part i un ::: tot sol la tanca; els dos atributs són opcionals (per defecte ''). Els blocs intermedis — normalment la llista de capítols — flueixen a la columna única de la pàgina de part amb bodyStyle, començant a margins.top; un cos més llarg que la pàgina continua en pàgines normals. La pàgina es classifica com a role: 'part' (VDTPage.partInfo porta el número i el títol), de manera que els elements de capçalera i peu poden adreçar-s'hi o saltar-se-la amb pages: 'part' / pages: 'body'; el backend PDF afegeix la part a l'esquema per sobre dels seus capítols. Un cos buit (:::part{…} seguit directament de :::) és el cas habitual i també produeix la pàgina — dues parts consecutives mai no en comparteixen una. Un :::part niat dins d'una altra part s'aplana a l'exterior.
Un tercer atribut, palette="<id>=<hex>[, <id>=<hex>…]", dona a la part els seus propis colors: a la pàgina de part i a totes les que la segueixen — fins a la part següent — cada color de disseny (capçalera, peu, banda d'obertura, dissenys de part i del seu verso) vinculat a un d'aquests ids de paleta pren el valor de la part en lloc del de la paleta del document. Així recoloregen les seccions d'un llibre la pestanya de la cantonada, el punt de la capçalera i la banda d'obertura de capítol sense un segon disseny: :::part{number="II" title="…" palette="band=#f6c297"}. El flux de text també ho segueix: en aquestes mateixes pàgines, tot color del flux igual al valor base d'una entrada de paleta substituïda —títols, colors de negreta, cursiva i referències, vinyetes i números de llista, etiquetes i barres de peu, text, filets i farciments de taules (capçalera, cos, files alternes i el farciment propi d'una cel·la), requadres (fons, vora, franja i títol) i xips (farciment, contorn i text)— pren el valor de la part, de manera que un headings.levels[1].color vinculat a band compon els títols de cada secció en el seu propi color. Les mostres de color en línia conserven el color que s'hi ha escrit. Els parells se separen amb comes, punts i comes o espais, = o : uneix id i color, i la # és opcional. Una part continua vigent un cop acabada la seva tanca: {partTitle}, {partNumber} i la paleta acompanyen el flux fins als capítols posteriors i — mitjançant continuation.part, que retorna continuationAfter() — fins als capítols compostos per separat, de manera que el segon capítol d'una secció mostra la secció a les capçaleres exactament igual que el primer.
Dues entrades de la paleta poden compartir valor base i tot i així prendre valors diferents en una part, quan la part en substitueix una i no l'altra o els dona colors diferents. El valor tot sol no indica de quina entrada ve un color del flux, així que cada color del flux s'aparella amb els ajustos dels quals pot venir i pren el valor al qual enllacen aquests ajustos. Es distingeixen:
- els colors del text d'un bloc: el color del text, els de negreta, cursiva i referències, la vinyeta o el número de llista i el separador que segueix el número. Amb
bodyText.colorvinculat ainkibodyText.boldColorvinculat aaccent, tots dos#1a1a1a, una part ambpalette="accent=#b8413d"recoloreja les negretes i deixa el text; si el que es vincula aaccentésunorderedLists.color, recoloreja les vinyetes i deixa el text dels elements; - cada nivell d'encapçalament, i cada estil d'encapçalament que fixa un color;
- el text d'una fila de l'índex, el seu número, i el seu número de pàgina i el seu subtítol;
- en cada estil de requadre, els farciments (fons, franja, pestanya de l'etiqueta), la vora, els filets (del marcador i de l'etiqueta) i el text (títol, icona, glif del marcador, etiqueta);
- cada color de cada estil de taula, de xip i de peu. Un estil de taula amb nom es distingeix de
tableStyle, i l'estil de peu d'un tipus de recurs decaptionStyle. El farciment propi d'una cel·la segueix el seu propi enllaç.
Queda un cas que es resol per valor: ajustos de llocs diferents que fixen el mateix color d'un bloc. Per exemple, bodyText.color, bodyText.blockquote.color, el color d'un estil de paràgraf i el body.color d'un estil de requadre fixen tots el color del text d'un bloc. Quan dos d'ells enllacen a entrades que comparteixen valor base i la part les separa, el color pren la substitució (l'última escrita, si se substitueixen totes dues). Dona a aquestes entrades valors base propis.
const resolved = resolvePartsConfig(config.parts, resolvedPage, resolvedBodyText, resolvedUnorderedLists, resolvedOrderedLists);
// => marges omplerts des de la pàgina, bodyStyle des del cos / les llistes
const minimal = stripPartsDefaults(config.parts);
// => undefined quan només queden valors estàtics per defecte#Estils d'encapçalament
La propietat headingStyles declara estils amb nom que un document aplica a un encapçalament amb # Título {style="<id>"}. Un estil fa dues coses. Sobreescriu la tipografia, el disseny i la numeració del nivell de l'encapçalament — qualsevol camp d'una entrada de nivell excepte level (font, cos, color, breakBefore, span, advancedDesign, textTransform, hidden, numberingTemplate…) — i governa la secció que obre l'encapçalament: les seves pàgines, fins al següent encapçalament del mateix nivell o superior, prenen les capçaleres, la geometria de pàgina, la tipografia de cos i la paleta de l'estil. Així els preliminars d'un llibre (un pròleg a una sola columna ampla, amb folis en romans i bandes blaves) conviuen en un manual a dues columnes i numeració decimal sense una segona configuració.
const config: PostextConfig = {
headingStyles: [
{
id: 'preliminar',
numbered: false,
breakBefore: { enabled: true, parity: 'odd' },
span: 'page',
advancedDesign: { enabled: true, minHeight: { value: 52, unit: 'mm' }, slot: { elements: [/* bandes, `{titleText}` */] } },
header: { elements: [/* foli | filet | `{title}. {subtitle}` */] },
margins: { left: { value: 50, unit: 'mm' }, right: { value: 17, unit: 'mm' } },
layout: { layoutType: 'single' },
bodyStyle: { fontSize: { value: 10.5, unit: 'pt' }, textAlign: 'justify' },
palette: { band: '#547396' },
},
],
};# Pròleg {style="preliminar"}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
id | string | — | Identificador al qual fa referència en una línia d'encapçalament. Un id desconegut deixa l'encapçalament tal com és. |
name | string | id | Nom llegible (només per a la interfície de l'editor). |
numbered | boolean | true | Si l'encapçalament compta: fa avançar el comptador del seu nivell (els números de numberingTemplate, el de la numeració de recursos), l'ordinal de capítol que hi ha darrere de i el número que imprimeix l'índex. false per a un pròleg, una llista d'autors, un índex: el primer capítol numerat que els segueix continua sent el capítol 1, i queda buit a les seves pàgines. |
toc | boolean | true | Si :::toc llista l'encapçalament. Un encapçalament ho sobreescriu amb / . |
runningChapter | boolean | true | Si un encapçalament de nivell 1 amb aquest estil passa a ser el capítol en curs: el que anomenen , , i les seves formes …AtTop a la seva pàgina i a les següents. false per a una làmina, un mapa o una portadella que es compon com a H1 dins d'un capítol: les capçaleres se'l salten, també a la seva pròpia pàgina, i continuen anomenant el capítol que interromp; tampoc no posa paraula guia h1. L'encapçalament continua comptant si és numbered (el seu propi disseny llegeix el seu propi ) i :::toc el continua llistant si és toc. Si a més porta toc: false, no té marcador al PDF: la làmina d'un capítol, a la pàgina que precedeix la seva obertura, deixa els marcadors del PDF als capítols. Els encapçalaments d'altres nivells no el llegeixen. Amb el valor per defecte, les pàgines que segueixen una làmina imprimeixen el títol de la làmina. Un prefaci o un pròleg amb numbered: false continua sent un capítol per si mateix i conserva el valor per defecte. L'opció només canvia el capítol que anomenen els marcadors: l'estil continua obrint una secció pròpia, com qualsevol estil d'encapçalament, de manera que fins al següent encapçalament de nivell 1 les pàgines prenen les ranures de capçalera, els marges, les columnes, l'estil del cos i la paleta de l'estil de la làmina (els del document on l'estil no fixa res), no els d'una secció amb estil que hagués obert el capítol interromput. En un llibre compost capítol a capítol, les capçaleres no passen d'un fitxer de capítol al següent, així que una làmina que obre un fitxer deixa buits els marcadors de capítol fins al primer encapçalament de capítol del fitxer. |
| camps de nivell | com a headings.levels[] | els valors del nivell | fontFamily, fontSize, lineHeight, fontWeight, italic, color, marginTop, marginBottom, snapToGrid, breakBefore, span, advancedDesign, textTransform, letterSpacing, hidden: cadascun que es fixi substitueix el valor del nivell per als encapçalaments d'aquest estil. breakBefore es combina camp a camp amb el del nivell: un estil que només fixa parity conserva l'enabled del nivell, i un que només fixa enabled: true conserva la seva paritat (fins a postext 1.4, el camp que faltava sortia en canvi del valor sense salt). |
numberingTemplate | string | la del nivell | Plantilla amb què es numeren els encapçalaments de l'estil, en lloc de la del seu nivell (els mateixos tokens que levels[].numberingTemplate). El comptador continua sent el del nivell: un estil d'apèndix amb 'Apèndix ' després de cinc capítols imprimiria Apèndix F, així que reinicia el compte amb al primer apèndix. '' no imprimeix número, tot i que l'encapçalament continua comptant: ni tan sols l'ordinal de capítol que l'índex i mostren per a un encapçalament de nivell 1 sense plantilla. El número apareix al flux, al del disseny de l'estil, a l'índex i a . |
header, footer | DesignSlot | els del document | Capçaleres i peus de les pàgines de la secció, en lloc de header / footer (els filtres parity i pages dels elements continuen aplicant-se). Una ranura buida els elimina. |
margins | PageMargins | marges de pàgina | Àrea de cos de les pàgines de la secció; cada costat hereta el marge de pàgina si no es fixa, mirror inclòs. Fa efecte a les pàgines que obre la secció — combina-ho amb breakBefore. |
layout | LayoutConfig | layout | Disposició de columnes de les pàgines de la secció (layoutType, gutterWidth…): una sola columna ampla per a un pròleg en un llibre a dues columnes. El seu columnRule es dibuixa a les pàgines de la secció, i cada camp que deixa sense fixar pren el valor de layout.columnRule del document, de manera que una secció que només canvia les columnes conserva el filet del document (vegeu Filet de columna). |
bodyStyle | PartsBodyStyleConfig | hereta bodyText | Tipografia dels paràgrafs, cites i llistes de la secció — els mateixos camps que parts.bodyStyle. |
palette | Record<string, string> | | Substitucions de paleta (id → hex) per a les pàgines de la secció, sobre les de la part en curs — el mateix mecanisme que l'atribut palette d'una part, i amb el mateix abast: no només les ranures de disseny compostes en aquestes pàgines (capçaleres, la banda d'obertura: tot color enllaçat a un id substituït), sinó també el flux de text, per valor: tot color del flux igual al valor base d'una entrada substituïda — encapçalaments, colors de negreta, cursiva i referències, pics i números de llista, etiquetes i barres de peu, text, filets i emplenaments de taula, requadres (fons, vora, franja i títol) i xips (emplenament, contorn i text) — pren el valor de la secció, igual que sota una part, també quan dues entrades comparteixen valor base (vegeu El contenidor :::part). Les mostres de color en línia conserven el color que hi ha escrit. |
Una secció es tanca al següent encapçalament del mateix nivell o superior: un # sense estil després d'un amb estil torna a les capçaleres i la geometria del document; un amb estil obre la seva pròpia secció. Les pàgines que la secció deixa en blanc per paritat li pertanyen, com passa amb els títols de capítol.
Un salt de pàgina no substitueix el salt del mateix encapçalament. Un estil hereta el breakBefore del seu nivell (el del nivell 1 és, per defecte, { enabled: true, parity: 'always-odd' }), i l'encapçalament l'aplica sigui on sigui, també just després d'un :::pagebreak. El salt de pàgina obre una pàgina nova, i l'encapçalament demana després, igualment, el seu costat del plec. Amb parity: 'odd', un salt que cau en una pàgina parella va seguit d'una pàgina en blanc, de manera que l'encapçalament obre a la següent senar. Amb 'always-odd' s'hi afegeix a més la pàgina separadora, i el salt de pàgina no canvia res, perquè l'encapçalament hauria obert aquesta pàgina de totes maneres. Un estil que ha de començar a la pàgina que obre un salt manual, com un índex darrere de la portada, desactiva el seu propi salt:
headingStyles: [
// Comença on el deixa el text: a la pàgina que va obrir el `:::pagebreak` anterior.
{ id: 'contents', numbered: false, toc: false, breakBefore: { enabled: false } },
],Per conservar una pàgina pròpia sense triar costat, fes servir en lloc d'això breakBefore: { parity: 'any' } i prescindeix del :::pagebreak.
A quina secció pertany una pàgina. Les capçaleres i la paleta es trien per pàgina, no per encapçalament. Una pàgina pren la secció vigent després de l'últim canvi de secció que hi ha: quan una secció acaba i una altra comença a la mateixa pàgina — dues lletres curtes d'un diccionari, per exemple —, la pàgina porta les capçaleres i la paleta de la segona; quan una secció amb estil acaba a mitja pàgina en un encapçalament sense estil, la pàgina torna a les del document. {chapterTitle} segueix la mateixa regla: una pàgina on es troben dos capítols mostra el títol del segon. Les pàgines en blanc segueixen la regla dels títols de capítol: una pàgina en blanc de paritat (blankForParity) pertany a la secció que s'obre després, i la separadora que afegeix un salt 'always-odd' / 'always-even' (blankForForce), a la secció anterior. Una pàgina divisòria de part tanca la secció oberta.
# A {style="letra"}
Abac, abella.
# B {style="letra"}
Babel, bavall… (continua a la pàgina següent)Les dues lletres comencen a la pàgina 1, així que la pàgina 1 pren les capçaleres de la secció B: una pestanya d'osca posada a la capçalera de l'estil hi diu «B», i cap pàgina no porta la pestanya de l'«A». Dona a cada secció una pàgina pròpia (breakBefore) quan totes necessiten la seva pestanya.
Apèndixs amb lletra després de capítols numerats, i una pàgina de dedicatòria que l'índex i els marcadors del PDF inclouen però que la pàgina no titula:
headingStyles: [
{ id: 'apendice', numberingTemplate: 'Apèndix {1:A}' },
{ id: 'silencioso', hidden: true, numbered: false },
],# Dedicatòria {style="silencioso"}
Per a M., que va llegir tots els esborranys.
# Mètode
…
# Qüestionari {style="apendice" startAt=1}
# Dades en brut {style="apendice"}Amb numberingTemplate: '{1}.' al nivell 1, els capítols imprimeixen 1., 2.…, i els apèndixs Apèndix A i Apèndix B. La dedicatòria obre la seva pàgina (el breakBefore del seu nivell), imprimeix només el seu paràgraf i continua apareixent com a Dedicatòria a :::toc, a les capçaleres amb {chapterTitle} i als marcadors del PDF; afegeix toc: false a l'estil per deixar-la fora de l'índex. Una làmina o un mapa que es compon com a H1 enmig d'un capítol demana el contrari que la dedicatòria: un estil amb runningChapter: false (normalment amb numbered: false i toc: false) manté les capçaleres en el capítol que interromp.
const resolved = resolveHeadingStylesConfig(config.headingStyles, resolvedPage, resolvedBodyText, resolvedUnorderedLists, resolvedOrderedLists);
// => sobreescriptures de nivell normalitzades, marges presos de la pàgina, bodyStyle del cos
const minimal = stripHeadingStylesDefaults(config.headingStyles);
// => undefined quan no queda cap estil#Índex de continguts
La propietat toc configura el que imprimeix una directiva :::toc (vegeu Format del document). L'índex es munta a partir de l'esquema del document — cada encapçalament amb el seu número i la seva etiqueta de pàgina, cada :::part — de manera que segueix els capítols: canvia el nom d'un, mou-lo a una altra part, canvia'n els autors, i les entrades canvien amb ell. Una entrada és el número de l'encapçalament en una columna pròpia, el títol, una línia de punts i l'etiqueta de pàgina a la vora dreta, i després una línia de subtítol opcional; una part és una fila dissenyada per parts.design.
const config: PostextConfig = {
toc: {
levels: [{ level: 1, fontWeight: 700, color: { hex: '#00507b', model: 'hex' }, numberWidth: { value: 7.4, unit: 'mm' } }],
unnumbered: { color: { hex: '#000000', model: 'hex' } },
pageNumber: { fontWeight: 400, width: { value: 8, unit: 'mm' } },
leader: { char: '.', gap: { value: 1, unit: 'mm' } },
subtitle: { enabled: true, attr: 'author', italic: true, fontSize: { value: 8.5, unit: 'pt' } },
parts: {
height: { value: 23, unit: 'pt' },
marginTop: { value: 11.5, unit: 'pt' },
design: { elements: [/* una caixa de banda, 'SECCIÓ {number}', '{titleText}', '{pageNumber}' */] },
},
},
};| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
levels | TocLevelConfig[] | nivell 1 | Nivells d'encapçalament llistats, cadascun amb la tipografia de les seves entrades: fontFamily, fontSize, lineHeight (per defecte l'interlineat del cos, perquè l'índex caigui a la retícula), fontWeight, italic, color, indent (de tota l'entrada), numberWidth / numberGap (la columna de números després de la qual comença el títol; els números s'hi alineen a la dreta, i un número més ample que numberWidth, com الفصل الحادي عشر o Capítol 12, eixampla la columna del seu nivell fins al més ample), numberFontFamily, numberFontSize, numberFontWeight, numberColor, marginTop, marginBottom. Els camps sense fixar hereten el text de cos. El número s'assenta a la línia de base de la primera línia del títol, siguin quins siguin la seva font i el seu cos, al llenç, a l'HTML i al PDF (fins a postext 1.4 es centrava a l'altura de la x com un pic, de manera que una font de retolació o un cos més gran quedaven per sobre del títol). Un renderitzador propi troba aquesta línia de base al bulletBaselineY del bloc de l'entrada; bulletY continua sent el centre vertical del quadratí del número, com a la 1.4, de manera que un renderitzador anterior a aquest camp dibuixa els números on sempre. |
unnumbered | TocEntryStyleConfig | — | Sobreescriptures per als encapçalaments l'estil dels quals declara numbered: false (un pròleg): no imprimeixen número i comencen arran a l'indent del nivell. |
pageNumber | objecte | font del nivell 1, pes del cos | fontFamily, fontSize, fontWeight, italic, color de l'etiqueta de pàgina, i width (per defecte 2em): la columna que se li reserva a la vora dreta, on s'alinea a la dreta. |
leader | objecte | | char es repeteix al llarg del buit entre el títol i el número de pàgina, alineat a la dreta perquè els punts d'entrades consecutives quedin en línia ('. ' els espaia); gap és el buit mínim entre el títol i la línia de punts. La línia porta tants caràcters com hi caben, mesurada com una tira sencera en la seva font, de manera que una font que separa amb el kerning els punts seguits posa menys punts en lloc de punts que arribin al número de pàgina. (Fins a postext 1.4 el compte sortia d'un sol punt, i amb una font així la línia de punts anava del títol fins a muntar-se sobre el número). Un títol que no deixés lloc a l'etiqueta salta de línia una mica abans. |
subtitle | objecte | | Una segona línia sota l'entrada presa d'un atribut de l'encapçalament (attr) — els autors del capítol — amb els seus propis fontFamily, fontSize, fontWeight, italic (per defecte true), color i indent addicional. La línia comparteix l'interlineat de l'entrada i mai no se separa del seu títol. |
parts.enabled | boolean | true | Si els separadors de part reben una fila. |
parts.breakBefore | boolean | false | Obre una pàgina nova abans de cada fila de part excepte la primera, de manera que els capítols de cada part es llisten en una pàgina pròpia. |
parts.design | DesignSlot | buit | Disseny de la fila; el seu contenidor és la fila (amplada de columna × height). Marcadors: , , …, i (l'etiqueta de la pàgina de part; amb parts.page: false, que no obre pàgina de part, l'etiqueta de la pàgina on comença el contingut de la part, on les capçaleres passen a la part; en un llibre maquetat capítol a capítol, una tanca que clou el seu capítol apunta a la primera pàgina amb contingut del capítol següent). Els colors lligats a la paleta prenen la palette de la mateixa part, així que la fila de cada secció surt amb el seu color. Buit, es componen (separats pel numberSeparator de l'H1) i el número de pàgina amb la tipografia de les entrades de nivell 1. |
parts.height, marginTop, marginBottom | Dimension | 2em, 0, 0 | Alçada de la fila i espai al seu voltant. em és el cos del text, així que la fila per defecte fa el doble del cos, no dues línies de text: amb un text de 9,5/13,5 pt fa 19 pt. Per a una fila de dues línies de text, dona l'alçada en pt (27pt en aquest cas). |
Les etiquetes de pàgina són les que imprimeix el document. buildDocument() torna a compondre un document amb :::toc amb les etiquetes de la passada anterior fins que s'estabilitzen (tres passades addicionals com a màxim); un amfitrió que compon un llibre capítol a capítol subministra en lloc d'això l'esquema del llibre sencer com a PostextContent.outline, muntat amb contentOutline() (encapçalaments i parts a partir del text) i outlineFromDoc() (les mateixes entrades amb les etiquetes de pàgina d'una composició), i torna a compondre el capítol de l'índex cada vegada que canvia l'outlineKey() d'aquest esquema. Cada entrada d'encapçalament porta el number que imprimeix l'índex i, en un encapçalament numerat, el seu counter: el compte acumulat del nivell després de qualsevol startAt, imprimeixi el que imprimeixi la plantilla — el que un amfitrió mostra al costat d'un capítol a les seves pròpies llistes.
#Índex analític
La propietat index configura el que imprimeix una directiva :::index (vegeu Format del document): els termes marcats amb :index[…] i :index{term="…"} al text, ordenats, agrupats per inicial (en xinès, per la inicial del pinyin o per traços; vegeu groupBy) i cadascun amb les pàgines on cau. Una entrada és el seu terme, un separador i els seus números de pàgina; al darrere van les seves subentrades, amb un pas de sagnat per nivell, i les línies que continuen una entrada pengen turnoverIndent per no alinear-se mai amb una subentrada.
const config: PostextConfig = {
headingStyles: [
// L'índex a dues columnes, sota el seu propi encapçalament.
{ id: 'indice', numbered: false, layout: { layoutType: 'double', gutterWidth: { value: 6, unit: 'mm' } } },
],
index: {
fontSize: { value: 8.5, unit: 'pt' },
lineHeight: { value: 11, unit: 'pt' },
rangeFormat: 'chicago',
groups: { fontFamily: 'Source Sans 3', fontWeight: 700, color: { hex: '#8a1c1c', model: 'hex' } },
},
};| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
fontFamily, fontSize, lineHeight, fontWeight, color | — | el text de cos | Tipografia de les entrades. Totes les línies de l'índex, lletres de grup incloses, es componen sobre lineHeight; l'índex porta el seu propi ritme i no s'ajusta a la retícula de base. |
indent | Dimension | 1em | Sagnat de cada nivell de subentrada. |
turnoverIndent | Dimension | 2em | Sagnat addicional de les línies que continuen una entrada, més enllà del seu nivell. |
entrySpacing | Dimension | 0 | Espai sobre cada entrada principal. |
separator, locatorSeparator, rangeSeparator | string | ', ', ', ', '–'; les dues primeres, '، ' en escriptura àrab | El que s'imprimeix entre el terme i la seva primera pàgina, entre dues pàgines i entre els extrems d'un interval. |
mergeRanges | boolean | true | Uneix en un interval les pàgines consecutives d'un mateix format de numeració: 12, 13, 14 s'imprimeix 12–14. Les pàgines principals no s'uneixen mai. |
rangeFormat | 'full' | 'chicago' | 'full' | Com s'escriu el segon número d'un interval: complet (234–237) o sense les xifres que comparteix amb el primer, com demana el Manual d'estil de Chicago (9.64): 71–72, 100–104, 101–8, 321–28, 1496–500. Les etiquetes romanes s'escriuen sempre completes. |
main | | negreta | Com es compon una pàgina principal (main a la marca). |
see | | segons la llengua, en cursiva (rodona en escriptura àrab) | Les paraules que precedeixen una remissió. Sense fixar, segueixen la llengua del document: Véase / Véase también, See / See also, Voir / Voir aussi, 见 / 另见 (見 / 另見 en xinès tradicional)… Un índex en xinès posa la remissió després d'un punt i sense espai: 贾琏 12。见贾政. |
locale | string | el del document | La llengua l'ordre alfabètic de la qual ordena les entrades (una etiqueta BCP 47, que llegeix Intl.Collator). En castellà la ñ va després de la n i encapçala un grup propi; els accents no alteren l'ordre. |
groupBy | 'auto' | 'letter' | 'pinyin' | 'stroke' | 'none' | 'auto' | Què encapçala cada grup. 'letter': la primera lletra de la clau d'ordenació. 'pinyin': una entrada que comença per un caràcter han va sota la inicial llatina de la seva lectura en pinyin (贾宝玉 sota J), i una clau d'ordenació llatina sota la seva lletra, darrere de les entrades xineses d'aquesta lletra (l'ordenador de cadenes posa les lletres llatines després dels caràcters xinesos): sort="jia mu" tanca la J. 'stroke': sota el nombre de traços del primer caràcter, 一畫, 二畫… (一画… en xinès simplificat). 'none': sense capçaleres; els símbols, les xifres i les paraules només se separen per groups.marginTop. 'auto' agrupa un índex en xinès simplificat (zh, zh-Hans, zh-CN) per pinyin, un en xinès tradicional (zh-Hant, zh-TW, zh-HK) per traços i qualsevol altra llengua per lletra. Les entrades s'ordenen amb la col·lació de la qual surten les capçaleres: un índex zh-Hant agrupat per pinyin s'ordena per pinyin. Les lectures i els traços són els de l'ordenador de cadenes (CLDR); si llegeix malament un caràcter (重 com a zhòng a 重阳, 行 com a xíng a 行业), dona a la marca una clau sort escrita amb caràcters que només tinguin la lectura cercada, que s'ordena al seu lloc: sort="崇阳" per a 重阳, sort="航业" per a 行业. Un navegador sense dades de col·lació xinesa imprimeix un índex per pinyin o per traços sense capçaleres. |
ignoreArticle | boolean | true en àrab | Ordena i agrupa les entrades àrabs com si no portessin l'article inicial ال (ٱل): البصرة va sota ب, entre بدر i بغداد, i s'imprimeix tal com està escrita. الله conserva el seu article, i una entrada amb clau sort pròpia s'ordena per aquesta clau tal qual. En qualsevol cas, un índex en àrab ignora els signes vocàlics i el tatweel, posa أ إ آ ٱ sota ا, i ordena ؤ com a و, ئ i ى com a ي, ة com a ه. |
groups.enabled | boolean | true | Imprimeix una capçalera sobre cada grup d'entrades (A, B…, o el nombre de traços, 0–9 per a les xifres, Símbolos per a la resta; 数字 / 數字 i 符号 / 符號 en xinès). |
groups.fontFamily, fontSize, fontWeight, italic, color | — | les de les entrades, pes 700 | La tipografia de la lletra de capçalera. Es compon sobre l'interlineat de les entrades. |
groups.marginTop | Dimension | una línia de l'índex | Espai sobre cada grup, amb lletra de capçalera o sense; cap sobre el primer grup, la distància del qual a l'encapçalament la fixa l'encapçalament, ni a dalt de tot d'una columna. |
groups.symbolsLabel, numbersLabel | string | segons la llengua; '0–9', '数字' / '數字' en xinès | Les capçaleres de les entrades que comencen per un símbol i per una xifra. |
Els números de pàgina surten de l'esquema del llibre, com els de l'índex de continguts: computeOutline() i contentOutline() enumeren les marques d'un text com a entrades de tipus 'indexMark' (amb indexMark.path, sort, see, seeAlso, main, range i index), i outlineFromDoc() dona a cadascuna la pàgina on va caure, que la composició registra a doc.indexMarks ({ sourceStart, pageIndex } per marca). buildDocument() torna a compondre un document que imprimeix el seu propi índex fins que els números s'estabilitzen; un amfitrió que compon un llibre capítol a capítol passa al capítol que conté :::index l'esquema del llibre sencer com a PostextContent.outline. tocOutline() i indexOutline() separen un esquema en allò que llegeixen l'índex de continguts i l'analític, perquè l'amfitrió associï cada capítol al que imprimeix: el sandbox torna a compondre el capítol de l'índex analític només quan es mou una marca, i el de l'índex de continguts només quan es mou un encapçalament. contentOutline() indica a més si un text imprimeix un índex analític (hasIndex).
#Unitats i colors
#Dimensions
Totes les mesures físiques a Postext fan servir el tipus Dimension — un valor aparellat amb una unitat:
interface Dimension {
value: number;
unit: DimensionUnit; // 'cm' | 'mm' | 'in' | 'pt' | 'px' | 'em' | 'rem'
}Unitats absolutes — cm, mm, in, pt, px — es converteixen a píxels amb els DPI configurats. A 300 DPI, 1 cm equival aproximadament a 118 px.
Unitats relatives — em, rem — s'escalen amb la mida de font actual. Un em és relatiu a la mida de font del mateix element; rem és relatiu a la mida de font del text de cos.
#Colors
Els colors s'emmagatzemen amb una representació hexadecimal i un model de color objectiu:
interface ColorValue {
hex: string; // '#ff0000', 'transparent', etc.
model: ColorModel; // 'hex' | 'rgb' | 'cmyk' | 'hsl'
}El camp model indica l'espai de color previst. Per a la renderització web, 'hex' o 'rgb' són els habituals. Per a fluxos de treball d'impressió, 'cmyk' preserva la intenció que el color s'ha d'especificar en CMYK en exportar a PDF.
Com que Postext apunta a una sortida de qualitat editorial, el color per defecte del text de cos es publica amb model: 'cmyk' (#000000). Els colors d'encapçalaments, negretes, cursives i llistes prenen per defecte el Color principal enllaçat a la paleta (#295AA3, model: 'hex'). El fons de pàgina i els indicadors d'interfície (retícula de base, marques de tall, indicadors de depuració) fan servir model: 'hex' per defecte. Sobreescriu color.model en qualsevol camp si necessites una altra semàntica d'exportació.
#Transparència
Un color pot ser translúcid. hex admet un canal alfa, com #rgba o #rrggbbaa. També admet un color rgb() / rgba(), amb la sintaxi de comes o la d'espais, i l'alfa com a nombre o com a percentatge. transparent és del tot transparent:
const config: PostextConfig = {
header: {
elements: [{
kind: 'box',
id: 'velo',
placement: {
anchor: { to: 'bleed', edge: 'top-left' },
size: { width: 'fill', height: { value: 40, unit: 'mm' } },
},
style: { backgroundColor: { hex: '#ffffffb3', model: 'hex' } }, // blanc al 70 %
}],
},
bodyText: { color: { hex: 'rgba(0, 0, 0, 0.85)', model: 'rgb' } },
};Els tres backends el pinten igual. El canvas i el visor HTML prenen el valor com un color CSS. El backend PDF fixa l'opacitat del color com un alfa constant, un ExtGState amb ca per als emplenaments i CA per als traços. Això abasta el text, els filets, les caixes, els emplenaments i vores de les taules, els xips, les mostres de color i les fórmules. Un color translúcid es compon sobre el que s'ha pintat abans. Una caixa de la capçalera o del peu es pinta l'última, així que vela el text que té a sota; una caixa d'una banda d'obertura es pinta la primera, així que tenyeix la pàgina sota el text. Quan el PDF es força a un altre espai de color (pdfGeneration.forceColorSpace amb colorSpace: 'cmyk' o 'grayscale'), el color es converteix i conserva el seu alfa. Al Sandbox, el lliscador d'opacitat del selector de color escriu aquests valors com a #rrggbbaa, i el selector també llegeix les altres formes.
#Fonts personalitzades
Postext resol cada fontFamily contra tots dos: el catàleg de Google Fonts i la llista customFonts del document. Les fonts personalitzades tenen prioritat en cas de coincidència de nom: si declares customFonts: [{ name: 'Roboto', … }], Postext farà servir el teu fitxer en lloc de la "Roboto" de Google Fonts.
Fes servir fonts personalitzades quan:
- El document requereix una tipografia de marca o amb llicència que no és a Google Fonts.
- L'entorn no pot arribar a la CDN de Google Fonts (sense connexió, intranet, sensibilitat de privadesa).
- Necessites mantenir el fitxer de font privat i evitar pujar-lo a un tercer.
#Esquema de configuració
type CustomFontFormat = 'woff2' | 'woff' | 'ttf' | 'otf';
type CustomFontStyle = 'normal' | 'italic';
interface CustomFontVariant {
weight: number; // CSS font-weight, 100..900
style: CustomFontStyle;
fileId: string; // id opac del binari en un emmagatzematge extern
format: CustomFontFormat;
fileName?: string; // nom original del fitxer (es mostra a la UI)
}
interface CustomFontFamily {
name: string; // s'usa on encaixaria un nom de Google Font
variants: CustomFontVariant[];
}
interface PostextConfig {
// ...
customFonts?: CustomFontFamily[];
}El binari de cada variant no s'incrusta a la configuració mateixa. La configuració només desa punters fileId; els bytes viuen fora d'ella. Al sandbox això vol dir IndexedDB (magatzem clau-valor, exclusiu del navegador, privat del document). Un integrador que incrusti Postext en un altre entorn és lliure de resoldre fileId com prefereixi —un endpoint de servidor, un service worker, el que sigui— sempre que els bytes arribin al fil principal abans de buildDocument.
#Gestionar fonts personalitzades al sandbox
Obre el tauler Fonts des de la barra d'activitat de l'esquerra (entre Recursos i Disseny). La seva llista Tipus de lletra d'aquest llibre mostra cada família que fa servir el disseny, amb la seva funció i si ve de Google Fonts o d'un fitxer propi. A Els teus fitxers de font, per a cada família:
- Afegir família — crea una família buida; canvia-li el nom en línia.
- Pujar variant(s) — tria un pes (100–900) i un estil (normal / italic), i selecciona un o diversos fitxers
.woff2,.woff,.ttfo.otf. Cada fitxer es converteix en una variant pròpia associada a la combinació (pes, estil) seleccionada; el nom del fitxer queda desat i es mostra a la fila per diferenciar les variants. Pots retocar el pes o l'estil d'una variant des dels seus desplegables en qualsevol moment. - Es permeten variants duplicades. Si dos fitxers cauen a la mateixa ranura (pes, estil), es desen tots dos i apareix un avís Duplicate font variant perquè ajustis els que sobren.
- Suprimir variant o Suprimir família — suprimeix l'entrada de la configuració i els bytes desats a IndexedDB.
Un cop declarada la família, cada selector de font l'agrupa sota Custom, per damunt de la llista de Google Fonts. Quan la tries, queda connectada a tots els camps fontFamily on l'apliquis.
#Comportament de renderització
Per dins:
- Quan
customFontscanvia, cada família declarada es registra automàticament com aFontFaceadocument.fonts— de manera que el visor HTML, el visor Canvas (que mesura a través dedocument.fonts) i qualsevol referència CSS directa agafen la cara personalitzada sense haver d'obrir abans el Font Picker. - El worker de composició rep els mateixos
ArrayBufferpel mateix canal de transferència de font payloads, així el mesurament (buildFontString, pretext) produeix mètriques idèntiques a les de Google Fonts. - Canviar o suprimir una variant descarta la cara desada a la memòria cau del worker per a aquella família, i es torna a registrar en el build següent, de manera que les previsualitzacions es mantenen sincronitzades amb el conjunt actual de variants.
- Exportació a PDF: els binaris pujats passen per la mateixa canalització
PdfFontProvider. Els.woff2es descomprimeixen; els.ttfi.otfes passen tal qual..woffes rebutja amb un missatge clar (pdf-lib no pot incrustar WOFF cru — torna a pujar-lo com a.woff2/.ttf/.otf). L'OpenType amb taules CFF (.otfamb la signaturaOTTO) s'incrusta sense subsetting, perquè el subsetter CFF de pdf-lib recorre cada glif en fersave()i es pot bloquejar durant minuts amb fonts reals; saltar-se el subset canvia una mica més de mida del PDF per temps de render constants.
#Avisos de fonts que falten
El tauler Revisió del Sandbox mostra al seu grup Fonts tres modes de fallada específics de les fonts personalitzades (tots governats pel mateix interruptor debug.warnings.missingFont que ja controla l'avís genèric "no carregada"):
- Família desconeguda — un
fontFamilyfa referència a un nom que no és ni una Google Font coneguda ni una família personalitzada declarada en aquest moment. També salta a l'instant quan suprimeixes una família personalitzada a la qual algunfontFamilyencara fa referència, sense esperar que el DOM se n'adoni. - Variant que falta — la família existeix però almenys una de les ranures estàndard (400 / 700, normal / italic) no té cap fitxer pujat. L'avís enumera les combinacions concretes que falten.
- Variant duplicada — dos o més fitxers comparteixen la mateixa ranura (pes, estil) dins d'una mateixa família. Només se n'usa un en renderitzar; l'avís et recorda que retoquis les entrades sobrants.
Si prems qualsevol dels avisos, s'obre el tauler Fonts perquè hi pugis la variant necessària, tornis a afegir la família o desfacis l'ambigüitat dels duplicats.
#Paleta de colors
La propietat colorPalette de PostextConfig permet definir un conjunt reutilitzable de colors amb nom i fer-hi referència des de qualsevol ColorValue de la configuració. És l'equivalent a Postext de les custom properties de CSS o del tauler de mostres d'InDesign: canvia l'entrada una sola vegada i tots els colors que hi apuntin s'actualitzen al document.
interface ColorPaletteEntry {
id: string; // identificador estable — referenciat per ColorValue.paletteId
name: string; // etiqueta llegible que es mostra a les UI del sandbox
value: ColorValue;
}#La paleta per defecte
Postext inclou una paleta per defecte amb una única entrada anomenada Color principal (id: 'main-color', hex #295AA3). Diversos valors per defecte — color dels encapçalaments, color de les negretes/cursives del cos, color dels :ref, color de les vinyetes i dels marcadors numèrics de les llistes — fan referència a aquesta entrada via paletteId: 'main-color', de manera que, en canviar aquesta única mostra, es retenyeix cada element del document que la utilitzi.
Pots inspeccionar, clonar o comparar la paleta per defecte amb tres exportacions:
import {
DEFAULT_COLOR_PALETTE,
cloneDefaultColorPalette,
isDefaultColorPalette,
} from 'postext';
// Instantània de només lectura de la paleta publicada.
DEFAULT_COLOR_PALETTE;
// => [{ id: 'main-color', name: 'Main Color', value: { hex: '#295AA3', model: 'hex' } }]
// Còpia independent — muta aquesta, no DEFAULT_COLOR_PALETTE.
const palette = cloneDefaultColorPalette();
// Comprova si l'usuari ha personalitzat la paleta.
isDefaultColorPalette(palette); // trueLa paleta viu al nivell superior de la configuració:
const config: PostextConfig = {
colorPalette: [
{ id: 'tinta', name: 'Tinta', value: { hex: '#0a0a0a', model: 'cmyk' } },
{ id: 'acento', name: 'Acento', value: { hex: '#b8860b', model: 'hex' } },
],
bodyText: { color: { hex: '#000000', model: 'cmyk', paletteId: 'tinta' } },
headings: { color: { hex: '#000000', model: 'hex', paletteId: 'acento' } },
};#Fer referència a una entrada de la paleta
Qualsevol ColorValue de la configuració pot portar un camp opcional paletteId que apunta a una entrada de colorPalette: fons de pàgina, colors del cos de text (inclòs el dels :ref), colors dels encapçalaments, filets de columna, colors de les llistes, colors de les taules, peus, chips i requadres (caixa, franja, icona, marcador, etiqueta, títol i cos), color de les marques de tall i de la retícula de base, indicadors de depuració, i tots els colors d'un disseny: les capçaleres i els peus, les obertures i els dissenys en columna dels encapçalaments, els dissenys i les capçaleres dels estils d'encapçalament, les pàgines de part i les files de part de l'índex (text, filet, farciment i vora de caixa, contorn, caplletra). Quan hi és, el hex / model de l'entrada de la paleta guanyen al hex / model emmagatzemats com a reserva. La reserva en línia només s'usa si la paleta falta, és buida o no conté aquell id — útil en exportar una configuració que llegirà una eina que no entengui paletes.
Canvi a postext 1.5. Fins a postext 1.4 la paleta només arribava a una llista fixa d'ajustos: els colors dels dissenys (capçaleres, obertures, estils d'encapçalament, parts, files de l'índex), bodyText.referenceColor, els colors de les etiquetes dels requadres i els de negreta i cursiva del seu cos conservaven el hex desat al costat del seu paletteId. Un document el valor desat del qual no coincideix amb l'entrada de la paleta —tots els editats al Sandbox després de canviar l'entrada, i tots els :ref quan el color principal no és #295AA3— ara imprimeix el color de la paleta, tal com indica l'enllaç. Per conservar un color tal com era, treu-ne el paletteId.
#Com s'apliquen les paletes
buildDocument executa la paleta en dos punts perquè els colors referenciats funcionin tant per a les sobreescriptures que hagis indicat com per als valors per defecte que es completen després:
applyPaletteToConfig(config)— resol cadaColorValuedel config original que portipaletteId. Útil per inspeccionar què veurà realment el motor.applyPaletteToResolvedConfig(resolved, palette)— s'executa després de resoldre els valors per defecte i reescriu els defaults enllaçats a la paleta (color dels encapçalaments, de la negreta/cursiva del cos, dels:ref, de les llistes, els colors dels dissenys per defecte) perquè coincideixin amb la paleta activa.
Totes dues recorren la configuració sencera, així que cap color enllaçat a la paleta no es queda enrere. La majoria dels colors del flux de text (cos, encapçalaments, llistes, taules, peus, chips, la caixa, el títol i el cos dels requadres) surten com a valors sense enllaç. Tots els altres —els colors dels dissenys, el dels :ref, les etiquetes dels requadres— agafen el hex / model de la paleta i conserven el seu paletteId. Aquest enllaç és el que l'atribut palette d'una part i el palette d'un estil d'encapçalament substitueixen a les seves pàgines (vegeu Parts), així que ha de sobreviure. htmlViewer.overrides es deixa tal qual: el visor HTML el fusiona primer, i la paleta que porti s'aplica llavors a tot, dissenys inclosos.
Rarament cal invocar-les, però totes dues estan exportades per a inspecció i reutilització:
import {
applyPaletteToConfig,
applyPaletteToResolvedConfig,
resolveColorValue,
} from 'postext';
const flat = applyPaletteToConfig(config);
// Cada ColorValue amb paletteId al config original porta ara el
// hex/model de l'entrada de la paleta (un color de disseny conserva el seu paletteId).
// `applyPaletteToResolvedConfig` normalment el gestiona buildDocument; fes-lo servir
// directament si construeixes un ResolvedConfig a mà i vols aplicar-hi la paleta.resolveColorValue(value, palette, fallback) és la variant per a un únic valor, útil quan compons configuracions de manera imperativa i has de resoldre un color solt.
#Editar la paleta
Un color el paletteId del qual no anomena cap entrada imprimeix el seu hex / model desat, que pot ser anterior al color que li donava l'entrada. Per això, abans de suprimir una entrada, convé reescriure cada ColorValue enllaçat a ella com un color sense enllaç amb el valor actual de l'entrada. La secció Paleta del Sandbox (Disseny → Colors) ho fa quan esborres una entrada, sigui on sigui el color (dissenys i etiquetes de requadre inclosos), i la seva confirmació enumera tots els ajustos que la fan servir: pel seu nom o per la seva ruta a la configuració (header.elements[2].color).
#Visor HTML
La propietat htmlViewer controla com el backend HTML disposa les pàgines a la pantalla. Només s'aplica quan renderitzes amb renderToHtml / renderToHtmlIndexed; els camins de canvas i PDF la ignoren completament — consumeixen directament els page.width, page.height i page.dpi configurats.
interface HtmlViewerConfig {
maxCharsPerLine?: number; // Amplada objectiu de columna, en caràcters de la font del cos.
columnGap?: number; // Espai horitzontal entre columnes en mode multicolumna (px).
optimalLineBreaking?: boolean; // Usar Knuth–Plass dins del visor HTML en lloc de greedy.
overrides?: HtmlViewerOverrides; // Configuració parcial només per a pantalla, fusionada sobre la del document.
}
type HtmlViewerOverrides = Omit<PostextConfig, 'htmlViewer'>;| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
maxCharsPerLine | number | 70 | Mesura objectiu de cada columna renderitzada, expressada en caràcters de la font del cos. El viewport mesura una cadena representativa de prosa d'aquesta longitud per obtenir l'amplada real en píxels — així el resultat s'adapta a qualsevol combinació de font proporcional i mida. |
columnGap | number | 50 | Espai horitzontal, en píxels CSS, entre columnes quan el visor és en mode multicolumna. S'ignora en mode d'una sola columna. |
optimalLineBreaking | boolean | false | Activa la divisió de línies Knuth–Plass dins del visor HTML. Està desactivat per defecte perquè el visor recompon la maquetació a cada resize o canvi de mida — l'algorisme greedy first-fit és prou ràpid perquè sembli instantani. Activa'l quan vulguis els mateixos talls òptims que fa servir el backend canvas. |
overrides | HtmlViewerOverrides | — | Una configuració parcial del document que només s'aplica a la pantalla. El visor HTML la fusiona sobre la configuració del document abans de compondre (applyHtmlViewerOverrides); canvas i PDF la ignoren. Els objectes es fusionen recursivament; una llista levels (encapçalaments, llistes, índex) es fusiona entrada a entrada per level; qualsevol altra llista — els elements d'un bloc de disseny, calloutStyles, colorPalette… — substitueix completament la llista base. Ús típic: un inici de capítol sense les bandes d'impremta, o una pàgina de part el títol de la qual s'ajusta contra el número en lloc d'una amplada fixa de caixa de tall. El sandbox l'edita com a JSON. |
const config: PostextConfig = {
headings: { levels: [{ level: 1, span: 'page', breakBefore: { enabled: true } }] },
htmlViewer: {
// A la pantalla, els capítols van seguits, sense l'inici a pàgina sencera.
overrides: { headings: { levels: [{ level: 1, span: 'column', breakBefore: { enabled: false } }] } },
},
};El resolver i l'stripper segueixen el mateix patró que les altres seccions:
import {
DEFAULT_HTML_VIEWER_CONFIG,
resolveHtmlViewerConfig,
stripHtmlViewerDefaults,
} from 'postext';
const resolved = resolveHtmlViewerConfig(config.htmlViewer);
// => { maxCharsPerLine: 70, columnGap: 50, optimalLineBreaking: false }
const minimal = stripHtmlViewerDefaults(config.htmlViewer);
// => undefined quan tot coincideix amb els valors per defecteConsulta Integrar el visor HTML més avall per veure'n un exemple complet.
#Generació de PDF (configuració)
La propietat pdfGeneration controla com el backend PDF emet el document final. Aquests ajustos els consumeix el paquet postext-pdf en el moment d'exportar; els visors canvas i HTML els ignoren.
buildDocument els porta al VDT, com a doc.config.pdfGeneration, i renderToPdf agafa cada ajust del primer lloc que el dona:
- les seves pròpies opcions (
outlines,accessible,colorSpace); - el
pdfGenerationdel primer document que rep (en un llibre, els ajustos del primer capítol valen per a tot el fitxer); - els valors per defecte: marcadors i etiquetatge activats, color RGB.
Així, renderToPdf(doc, { fontProvider }) segueix la configuració, i una opció que es passa a renderToPdf mana només en aquell ajust. forceColorSpace i colorSpace equivalen junts a l'opció colorSpace: el colorSpace de la configuració s'aplica mentre forceColorSpace està activat, i amb aquest desactivat el PDF surt en RGB. Les versions anteriors de postext-pdf només llegien les opcions; una configuració amb pdfGeneration ara canvia el PDF de qui crida sense opcions.
type PdfColorSpace = 'rgb' | 'cmyk' | 'grayscale';
interface PdfGenerationConfig {
outlines?: boolean; // Emetre marcadors PDF a partir de l'arbre d'encapçalaments.
forceColorSpace?: boolean; // Convertir tots els colors a `colorSpace`.
colorSpace?: PdfColorSpace; // Espai de destinació quan `forceColorSpace` és true.
accessible?: boolean; // Sortida etiquetada orientada a PDF/UA (arbre d'estructura, text alternatiu, llengua).
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
outlines | boolean | true | Emet outlines (marcadors) PDF a partir de la jerarquia d'encapçalaments, de manera que els lectors puguin saltar directament a qualsevol encapçalament des de la barra lateral del visor PDF. Desactiva'l per a documents en què l'arbre d'encapçalaments no aporta res (per exemple, pòsters d'una sola pàgina). |
forceColorSpace | boolean | false | Quan és true, tots els colors del PDF renderitzat es converteixen a colorSpace en exportar. Deixa'l desactivat en PDF pensats per a pantalla si els colors d'entrada ja són a l'espai desitjat; activa'l per garantir un únic espai de color partint de fonts heterogènies. |
colorSpace | 'rgb' | 'cmyk' | 'grayscale' | 'cmyk' | Espai de color de destinació que s'usa quan forceColorSpace està activat. Fes servir 'cmyk' per a impremta òfset, 'rgb' per a PDF només de pantalla i 'grayscale' per a proves en blanc i negre. No té cap efecte si forceColorSpace és false. |
accessible | boolean | true | Genera un PDF accessible i etiquetat, orientat a PDF/UA-1: un arbre d'estructura lògica en ordre de lectura (títols que mai no salten de nivell, paràgrafs, llistes, cites, callouts, taules amb cel·les de capçalera, figures amb el seu text alternatiu i el seu peu, fórmules, referències com a enllaços, l'índex d'un :::toc com un sol TOC amb un TOCI per fila: el número de la fila com a Lbl, el seu títol i la seva pàgina com un Reference que conté l'enllaç), el títol i la llengua del document (el locale de primer nivell), la identificació PDF/UA a les metadades XMP, i tota marca decorativa (fons de pàgina, filets, retícula de base, capçaleres i peus corrents, marques de tall, capçaleres de taula repetides, el títol repetit i l'indicador de continuació d'un avís partit) assenyalada com a artefacte perquè els lectors de pantalla l'ometin. Una figura sense altText fa servir el seu peu i, si no en té, la seva etiqueta. Una figura o una taula flotant es llegeix just després del text que la cita per primera vegada, o del text anterior a la seva línia ::resource, i un requadre flotant després del text anterior a la seva obertura, encara que el flotant quedi en una pàgina posterior; una llista o un índex que continuen després d'un flotant queden en un sol element. Desactiva'l només per a màsters d'impremta on l'estructura addicional sobri. |
pdfGeneration: {
outlines: true,
accessible: true,
forceColorSpace: true,
colorSpace: 'cmyk',
}El resolver i l'stripper segueixen el mateix patró que les altres seccions:
import {
DEFAULT_PDF_GENERATION_CONFIG,
resolvePdfGenerationConfig,
stripPdfGenerationDefaults,
} from 'postext';
const resolved = resolvePdfGenerationConfig(config.pdfGeneration);
// => { outlines: true, forceColorSpace: false, colorSpace: 'cmyk', accessible: true }
const minimal = stripPdfGenerationDefaults(config.pdfGeneration);
// => undefined quan tot coincideix amb els valors per defecteConsulta Generació de PDF més avall per a la recepta completa d'exportació.
#Visor Folio (configuració)
La propietat folio decideix com presenta el visor Folio (postext-folio) el llibre imprès en 3D: l'angle de la vista, el paper, l'enquadernació, la superfície sobre la qual reposa el llibre i la llum. La composició la ignora, igual que la sortida canvas, HTML i PDF. buildDocument porta els ajustos resolts al VDT, com a doc.config.folio, quan la configuració en fixa algun, així que un document sense ajustos conserva el hash de la seva composició.
interface FolioConfig {
tilt?: number; // Graus des de la vertical, 0–70.
yaw?: number; // Graus al voltant del llibre, −180–180.
paper?: {
type?: 'uncoated' | 'bookWove' | 'coatedMatte' | 'coatedSilk' | 'coatedGloss'
| 'bible' | 'newsprint' | 'cardStock' | 'board';
grammage?: number; // g/m²
bulk?: number; // cm³/g; gruix µm = grammage × bulk
finish?: 'auto' | 'uncoated' | 'matte' | 'silk' | 'gloss';
texture?: 'auto' | 'smooth' | 'vellum' | 'wove' | 'laid' | 'linen' | 'felt';
textureStrength?: number; // 0–2
shade?: ColorValue;
showThrough?: boolean;
};
binding?: {
type?: 'hardcover' | 'paperback' | 'sewn' | 'layflat' | 'saddleStitch';
cover?: 'case' | 'pages';
coverMaterial?: 'auto' | 'cloth' | 'paper' | 'leather';
coverColor?: ColorValue;
spineImage?: string; // id de recurs
};
surface?: {
type?: 'oak' | 'walnut' | 'linen' | 'felt' | 'leather' | 'marble' | 'plain' | 'none';
color?: ColorValue;
};
lighting?: {
environment?: 'studio' | 'daylight' | 'lamp' | 'overcast' | 'night';
intensity?: number; // 0.25–2
shadows?: boolean;
};
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
tilt | number | 22 | Angle de la vista respecte a la vertical, en graus, limitat a 0–70. Amb 0 el llibre obert es veu pla des de dalt; amb un angle més gran el peu de les pàgines s'acosta i s'aprecia el gruix del bloc de pàgines. |
yaw | number | 0 | Quant gira la vista al voltant del llibre, en graus, portat a −180–180. Amb 0 el llibre es veu des del peu de les seves pàgines; un angle positiu porta la mirada cap a la seva dreta i un de negatiu cap a la seva esquerra. Juntament amb tilt és la vista amb què s'obre el visor i a la qual torna resetView(). |
paper.type | FolioPaperType | 'uncoated' | El tipus de paper. Dona els valors per defecte dels cinc camps següents (vegeu la taula de papers). cardStock és cartolina de coberta; board és cartró rígid, com el d'un llibre de cartró, i els seus fulls passen sense doblegar-se. |
paper.grammage | number | el del paper | Gramatge en grams per metre quadrat, 20–2500. Un paper de més gramatge és més gruixut, més rígid i més opac: el full es corba més obert i transparenta menys el revers. |
paper.bulk | number | el del paper | Mà: gruix per unitat de pes, en cm³/g, 0,5–3. El gruix d'un full en micres és gramatge × mà, i d'aquest valor i del nombre de pàgines en surt el llom del bloc. |
paper.finish | FolioPaperFinish | 'auto' | Sense estucar (fibra, sense brillantor) o estucat i calandrat fins a mat, semimat (silk, una brillantor suau) o brillant. 'auto' agafa el del paper. |
paper.texture | FolioPaperTexture | 'auto' | El relleu de la superfície: smooth (llisa, calandrada), vellum (vitel·la, un gra fi), wove (la textura uniforme de gairebé tots els papers de llibre, formada sobre una tela metàl·lica teixida), laid (verjurat: verjures juntes creuades per corondells més separats), linen (tela, un gofrat de fils creuats), felt (les marques irregulars d'un feltre). 'auto' agafa la del paper. |
paper.textureStrength | number | 1 | Quant es marca la textura amb la llum, 0–2. |
paper.shade | ColorValue | el del paper | El color del paper abans d'imprimir (blanc, natural, color d'os). Les pàgines s'imprimeixen damunt d'aquest color. |
paper.showThrough | boolean | true | El revers de la pàgina es veu tènuement a través del paper fi. |
binding.type | FolioBindingType | 'hardcover' | hardcover: tapa dura (cartoné), tapes una mica més grans que les pàgines. paperback: rústica fresada (llom fresat i encolat), s'obre menys. sewn: rústica cosida. layflat: enquadernació plana, s'obre sense enfonsar-se al llom. saddleStitch: grapat a cavallet, plecs doblegats i grapats pel plec, com una revista o un fullet; sense llom pla. |
binding.cover | FolioCoverSource | 'case' | Les cobertes. 'case' dibuixa una tapa al voltant de les pàgines. 'pages' agafa la primera pàgina del llibre com a cartró davanter i l'última, si és parella, com a cartró posterior: el llibre està tancat fins que es passa la coberta, els cartrons giren rígids i no es dibuixa cap tapa. |
binding.coverMaterial | FolioCoverMaterial | 'auto' | 'auto' és tela en la tapa dura i cartolina ('paper') en les altres enquadernacions. |
binding.coverColor | ColorValue | blau fosc (#2c3e57) | El color del material de la coberta. |
binding.spineImage | string | cap | L'id d'un recurs de mapa de bits o SVG imprès al llom: el llom tal com es veu amb el llibre dret, el cap a dalt i la coberta a la dreta. S'ajusta fins a cobrir el llom, centrat. El grapat a cavallet no el fa servir. |
surface.type | FolioSurfaceType | 'oak' | Sobre què reposa el llibre. 'none' deixa el fons de l'amfitrió. |
surface.color | ColorValue | cap | Tenyeix la superfície; amb 'plain' és el seu color. |
lighting.environment | FolioEnvironment | 'studio' | L'entorn que reflecteixen els papers estucats i brillants, juntament amb la llum principal que projecta les ombres. |
lighting.intensity | number | 1 | Exposició, 0,25–2. |
lighting.shadows | boolean | true | Ombres de la llum principal. |
Els papers i els valors que aporten (FOLIO_PAPER_STOCKS), habituals a les fitxes tècniques dels fabricants:
| Paper | Gramatge | Mà | Gruix | Acabat | Textura | To |
|---|---|---|---|---|---|---|
uncoated (òfset sense estucar) | 90 g/m² | 1.25 | 113 µm | sense estucar | uniforme | #fcfbf8 |
bookWove (paper de llibre color d'os, mà alta) | 80 g/m² | 1.6 | 128 µm | sense estucar | uniforme | #f6efdc |
coatedMatte (estucat mat) | 115 g/m² | 1.0 | 115 µm | mat | llisa | #fdfdfc |
coatedSilk (estucat semimat) | 115 g/m² | 0.9 | 104 µm | semimat | llisa | #ffffff |
coatedGloss (estucat brillant) | 115 g/m² | 0.8 | 92 µm | brillant | llisa | #ffffff |
bible (paper bíblia) | 40 g/m² | 1.1 | 44 µm | sense estucar | vitel·la | #f9f6ee |
newsprint (paper de diari) | 48 g/m² | 1.5 | 72 µm | sense estucar | uniforme | #ebe7dc |
cardStock (cartolina) | 250 g/m² | 1.2 | 300 µm | sense estucar | vitel·la | #fbfaf6 |
board (cartró) | 1250 g/m² | 1.6 | 2000 µm | semimat | llisa | #ffffff |
Una novel·la en paper color d'os, en rústica fresada, damunt d'una taula de noguera sota un llum de lectura:
folio: {
paper: { type: 'bookWove' },
binding: { type: 'paperback', coverColor: { hex: '#8a2b1f', model: 'hex' } },
surface: { type: 'walnut' },
lighting: { environment: 'lamp' },
}Els colors segueixen els enllaços a la paleta com qualsevol altre color de la configuració (paletteId). El resolvedor i el netejador funcionen com a les altres seccions; el netejador treu els valors del paper que coincideixen amb els del tipus triat:
import { FOLIO_PAPER_STOCKS, DEFAULT_FOLIO_CONFIG, resolveFolioConfig, stripFolioDefaults } from 'postext';
resolveFolioConfig({ paper: { type: 'bible' } }).paper;
// => { type: 'bible', grammage: 40, bulk: 1.1, finish: 'uncoated', texture: 'vellum', textureStrength: 1, shade: { hex: '#f9f6ee', … }, showThrough: true }
stripFolioDefaults({ paper: { type: 'bible', grammage: 40 } });
// => { paper: { type: 'bible' } }Al Sandbox aquests ajustos formen el grup Folio del tauler Disseny (Disseny → Folio → Visor Folio (3D)), i la pestanya Folio els mostra a mesura que els canvies, sense tornar a compondre el llibre. El visor en si es descriu a Un llibre en 3D, i una tanda de pàgines en un altre paper, a Format del document › :::paper.
#Depuració
La propietat debug agrupa dos tipus d'ajudes d'autoria: superposicions visuals que mantenen sincronitzats el text font i la composició renderitzada, i un conjunt d'avisos que mostren al tauler Revisió del Sandbox els problemes tipogràfics o estructurals del document. Cap dels dos no afecta la sortida exportada.
| Propietat | Tipus | Descripció |
|---|---|---|
cursorSync | SyncIndicatorConfig | Cursor reflectit a la composició renderitzada — vegeu Superposicions visuals. |
selectionSync | SyncIndicatorConfig | Selecció de la font ressaltada a la pàgina — vegeu Superposicions visuals. |
looseLineHighlight | LooseLineHighlightConfig | Superposició sobre les línies justificades fluixes — vegeu Superposicions visuals. |
pageNegative | | Negatiu d'alt contrast de la pàgina — vegeu Superposicions visuals. |
warnings | WarningsToggleConfig | Un booleà per cada classe d'avís d'autoria que mostra l'editor — vegeu Avisos. |
#Superposicions visuals
| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
cursorSync.enabled | boolean | true | Mostra un cursor en la composició renderitzada que reflecteix la posició del cursor a la font. |
cursorSync.color | ColorValue | #2563eb | Color d'aquest cursor. |
selectionSync.enabled | boolean | true | Ressalta l'interval renderitzat que coincideix amb la selecció a la font. |
selectionSync.color | ColorValue | #fde04780 | Color del ressaltat — un groc translúcid per defecte. |
looseLineHighlight.enabled | boolean | false | Pinta una superposició sobre les línies justificades amb un espaiat entre paraules que supera threshold vegades l'amplada de l'espai normal. |
looseLineHighlight.color | ColorValue | #ff000040 | Color d'aquesta superposició. |
looseLineHighlight.threshold | number | 3 | Multiplicador de l'amplada de l'espai normal a partir del qual una línia justificada compta com a fluixa. L'avís looseLines fa servir el mateix llindar. Una línia justificada amb espais que passarien de 3 vegades la seva amplada es compon en bandera, de manera que amb el valor per defecte la capa i l'avís gairebé no troben res en el text corregut; abaixa'l (1.5 o 2) per veure les línies fluixes que continuen justificades. |
pageNegative.enabled | boolean | false | Renderitza una superposició en negatiu d'alt contrast sobre la pàgina — útil per auditar visualment la forma general d'una doble pàgina (densitat de text, equilibri de columnes, espai en blanc) d'un cop d'ull, sense distreure't amb el detall dels glifs. |
Cada SyncIndicatorConfig és { enabled: boolean; color?: ColorValue }. LooseLineHighlightConfig és { enabled: boolean; color?: ColorValue; threshold?: number }. pageNegative és un simple interruptor { enabled: boolean }.
debug: {
cursorSync: { enabled: true, color: { hex: '#ff0066', model: 'hex' } },
selectionSync: { enabled: false, color: { hex: '#fde04780', model: 'hex' } },
looseLineHighlight: { enabled: true, color: { hex: '#ff000040', model: 'hex' }, threshold: 3 },
pageNegative: { enabled: true },
}Aquestes superposicions les dibuixa el Sandbox sobre la seva vista Canvas. No formen part de la pàgina: ni renderPage, ni la sortida HTML, ni el PDF no les pinten mai.
#Línies fluixes al teu propi canvas
El motor exporta el ressaltat de línies fluixes com a dues funcions auxiliars, per a una pàgina que pintes tu:
import { buildDocument, renderPageToCanvas, drawLooseLines, findLooseLines } from 'postext';
const doc = buildDocument(content, config);
const canvas = document.querySelector('canvas')!;
renderPageToCanvas(doc.pages[0], doc, canvas, { scale: 0.5 });
drawLooseLines(canvas.getContext('2d')!, doc.pages[0], doc, { threshold: 2.5 });
// Les mateixes línies com a dades: un informe, una capa SVG, un recompte per pàgina.
for (const { ratio, line, block } of findLooseLines(doc, { threshold: 2.5 })) {
console.log(`pàgina ${block.pageIndex + 1}: ${ratio.toFixed(2)}× — ${line.text}`);
}findLooseLines(doc, { threshold?, pageIndex? })retorna, en ordre de lectura, cada línia justificada amb unjustifiedSpaceRatioque superathreshold:{ block, line, ratio, x, y, width, height }. El rectangle, en píxels de pàgina, és la franja que cobreix el ressaltat: tota l'amplada del bloc a l'alçada de la línia. Són les línies que el Sandbox ressalta i que el seu tauler Revisió assenyala com alooseLine.drawLooseLines(ctx, page, doc, { threshold?, color? })omple aquestes franges en una pàgina i retorna les línies que ha pintat. Dibuixa en píxels de pàgina amb la transformació actual del context, així que crida-la just després derenderPageorenderPageToCanvassobre el mateix canvas: totes dues deixen el context escalat a la pàgina.colorés qualsevol estil d'emplenament del canvas.- Valors per defecte. Les dues funcions fan servir el llindar (3) i el color (
#ff000040) per defecte, no eldebug.looseLineHighlightdel document: aquest ajust és del Sandbox. Per seguir una configuració, passaresolveDebugConfig(config.debug).looseLineHighlight.thresholdi.color.hex.
#Avisos
debug.warnings controla quins problemes d'autoria apareixen al tauler Revisió del Sandbox (s'edita a Disseny → Avançat → Avisos). Cada clau és un interruptor booleà independent; posa'n una a false per silenciar aquell avís concret sense desactivar els altres.
Aquests interruptors només filtren el tauler del Sandbox. Els avisos que registra el mateix motor —les caixes que desborden la seva columna a doc.warnings; els ids de recurs, directives, insercions i ids d'estil desconeguts i les quadrícules de taula irregulars a doc.contentWarnings— hi són diguin el que diguin els interruptors, i els renderitzadors notifiquen les imatges que pinten com a marcador de posició; vegeu Avisos del document.
interface WarningsToggleConfig {
missingFont?: boolean;
looseLines?: boolean;
headingHierarchy?: boolean;
consecutiveHeadings?: boolean;
listAfterHeading?: boolean;
designIssues?: boolean;
}| Propietat | Tipus | Per defecte | Descripció |
|---|---|---|---|
missingFont | boolean | true | Avisa quan una font referenciada per la configuració no s'ha pogut carregar al navegador. Detecta errates a fontFamily i paquets @fontsource/... absents abans que es converteixin en substitucions silencioses per una font de reserva a la sortida renderitzada. |
looseLines | boolean | true | Avisa de les línies justificades amb un espaiat entre paraules que supera debug.looseLineHighlight.threshold. Complementa la superposició: l'avís les enumera al tauler, la superposició les mostra a la seva posició. |
headingHierarchy | boolean | true | Avisa de nivells d'encapçalament que salten un rang — per exemple, un H1 seguit directament d'un H3. Els salts en la jerarquia solen indicar o bé una errata en la profunditat de l'encapçalament o bé un malentès sobre l'esquema del document. |
consecutiveHeadings | boolean | false | Avisa quan un encapçalament va seguit immediatament d'un altre encapçalament, sense cap paràgraf ni llista entremig. Desactivat per defecte perquè els encapçalaments encadenats són legítims en moltes plantilles (títol + subtítol, capítol + epígraf); activa'l en manuscrits on cada encapçalament ha d'introduir prosa. |
listAfterHeading | boolean | false | Avisa quan una llista comença immediatament després d'un encapçalament, sense paràgraf introductori. Desactivat per defecte perquè el material de referència sol fer-ho; activa'l en escriptura narrativa on cada llista hauria d'estar emmarcada per prosa. |
designIssues | boolean | true | Avisa de problemes d'integritat a les ranures de disseny — capçaleres de pàgina, peus de pàgina, l'obertura de part i el verso en blanc que la segueix, les files de part de l'índex, ranures de disseny avançat dels encapçalaments, i el disseny i les capçaleres i peus de secció de cada estil de títol. Cobreix cadenes d'ancoratge cícliques i referències d'ancoratge penjants (un element ancorat a un #id que ja no existeix), un encapçalament amb span de pàgina amb el breakBefore desactivat, i un disseny avançat habilitat amb elements que no renderitzen mai . |
debug: {
warnings: {
missingFont: true,
looseLines: true,
headingHierarchy: true,
consecutiveHeadings: true,
listAfterHeading: false,
designIssues: true,
},
}A més d'aquests, el tauler mostra sempre els avisos que emet la mateixa maquetació (VDTDocument.warnings), com ara una caixa d'avís que desborda la seva columna (calloutOverflow), i els valors de configuració que el motor va substituir (VDTDocument.configWarnings, o collectConfigWarnings(config); vegeu Avisos de configuració més avall).
#Avisos de configuració
Sis errors de la configuració mateixa no passen mai en silenci, i cap interruptor no els amaga. El motor no falla per cap d'ells: substitueix el valor, o prescindeix de l'ajust, i ho diu.
- Format de numeració desconegut: un
numberFormatde llista numerada, unpage.pageNumbering.formato elcounterFormatd'un tipus de recurs que no és cap de les grafies dels formats de numeració. Numera en decimal. - Llista de fonts en una família: un
fontFamily(o qualsevol…FontFamily) que conté una llista de fonts CSS. El text es compon en la primera família de la llista (vegeu Una sola família perfontFamily). - Columna lateral sense lloc: un
sideColumnPercentd'una disposició'oneAndHalf'(la del document, o ellayoutpropi d'un estil d'encapçalament) que deixaria alguna de les columnes per sota de l'1% de l'amplada de l'àrea de contingut, o que no és un nombre. Les columnes es tallen al valor més proper que admeten totes dues, iusedho indica (sideColumnPercentClamped; vegeu la disposició'oneAndHalf'). - Retícula de caràcters massa gran: un
cjk.gridamb més caràcters per línia o més línies per pàgina dels que caben entre els marges. La retícula es compon amb els que hi caben, iusedn'indica el nombre (cjkGridClamped; vegeu Retícula de caràcters). - Ajust d'encapçalament desconegut: una clau que no existeix a
headings,headings.balancing, un nivell d'encapçalament, un estil d'encapçalament o un estil de paràgraf: unletterSpacngmal escrit, untrackingpres d'una altra eina, unlevelen un estil d'encapçalament, unfontStyle: 'italic'en un estil de paràgraf (que portaitalic: true). El motor la ignora (fins a postext 1.4 ho feia sense dir res).valueés la clau,usedva buit isuggestionanomena l'ajust al qual s'assembla més, quan en dista una o dues lletres o només canvia en majúscules (unknownConfigKey). - Valor d'ajust desconegut: un ajust que admet unes poques paraules en porta una altra, com ara
direction: 'right'(admetauto,ltrortl). El motor llegeix en el seu lloc el valor per defecte, iuseddiu en què ha quedat: per adirection, la direcció de la llengua del document (unknownConfigValue).
El Sandbox els mostra al tauler Revisió amb la ruta de l'ajust. En codi, buildDocument els deixa al document com a configWarnings (absent quan la configuració està neta), i collectConfigWarnings(config) els retorna sense compondre res:
import { buildDocument, collectConfigWarnings } from 'postext';
// JavaScript sense tipus: en TypeScript, 'roman' ni tan sols passa la comprovació de tipus.
const config = { bodyText: { fontFamily: 'EB Garamond, serif' }, orderedLists: { numberFormat: 'roman' } };
const doc = buildDocument({ markdown }, config);
doc.configWarnings;
// [{ kind: 'fontFamilyStack', path: 'bodyText.fontFamily', value: 'EB Garamond, serif', used: 'EB Garamond' },
// { kind: 'unknownNumberFormat', path: 'orderedLists.numberFormat', value: 'roman', used: 'arabic' }]
collectConfigWarnings(config); // la mateixa llistaTambé es revisa cada configuració parcial niada: estils d'encapçalament, les llistes dins de les parts, htmlViewer.overrides, elements de disseny.
formatWarning (vegeu Avisos del document) també els descriu, amb la ruta de l'ajust al davant —bodyText.fontFamily: font stack "EB Garamond, serif" — set in "EB Garamond", headingStyles[0].letterSpacng: unknown setting "letterSpacng" — ignored (did you mean "letterSpacing"?)—, de manera que un amfitrió pot registrar en un sol bucle les tres llistes que retorna la composició.
#Ús programàtic
Camí recomanat: fes servir el Web Worker. Al navegador, la immensa majoria de les integracions han d'executar el pipeline a través de
createLayoutWorker()depostext/worker, no cridantbuildDocumentdirectament al fil principal. El worker manté la UI fluida durant els builds, desa a la memòria cau els mesuraments de text entre reconstruccions incrementals i connecta la cancel·lació last-wins perquè una nova pulsació avorti qualsevol build obsolet en curs. Salta directament a Executar la composició en un Web Worker per a la recepta canònica. Tot el que hi ha a la resta d'aquesta secció (cridarbuildDocumentdirectament, resolvers, strippers, memòries cau) continua sent útil — el worker exposa exactament les mateixes entrades i sortides — però per a codi d'UI l'embolcall del worker és el punt de partida correcte. Recorre abuildDocumental fil principal només per a exportacions puntuals, renderitzat al servidor (Node) o tests.
#Construir un document
La funció buildDocument executa el pipeline de composició complet i retorna un Arbre Virtual del Document (VDT) amb coordenades precises per a cada element. És el punt d'entrada de més baix nivell; el codi d'UI hauria de preferir l'embolcall Web Worker, que crida buildDocument dins d'un fil worker dedicat amb els mateixos arguments.
import { buildDocument } from 'postext';
const content = {
markdown: '# Capítol u\n\nLa història comença aquí...',
};
const config = {
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt sobreescriu el valor per defecte de 8 pt
};
// Construir la composició — produeix un VDT amb una entrada per pàgina a `vdt.pages`
const vdt = buildDocument(content, config);
console.log(`El document té ${vdt.pages.length} pàgines`);#Avisos del document
buildDocument no s'atura davant d'una referència errònia o un estil desconegut: aplica una alternativa i registra el que ha fet a doc.contentWarnings. Les caixes que la maquetació va haver de forçar són a doc.warnings, que conserva la forma que tenia a postext 1.4: cada entrada és un calloutOverflow amb el seu pageIndex, el seu columnIndex i el seu overflowPx. Cada camp falta quan no hi ha res a notificar. Cada entrada té un kind. Els tipus de contingut porten l'interval d'origen de la construcció —sourceStart / sourceEnd, desplaçaments en el markdown que has passat, frontmatter inclòs— i, quan la construcció ha caigut en una pàgina, el seu pageIndex.
| Tipus | Es registra quan | Què fa la sortida |
|---|---|---|
calloutOverflow | Una caixa :::callout no cap en cap columna i cap tall no la pot partir. | Es col·loca igualment, desbordant la seva columna en overflowPx (a pageIndex / columnIndex). És l'únic tipus que apareix a doc.warnings; els de sota són a doc.contentWarnings. |
unknownResourceId | Una inserció ::resource (usage: 'embed'), una referència en línia :ref ('ref') o la imatge d'una cel·la de taula ('cellImage') anomena un id que no té cap recurs. | La inserció s'omet, la referència imprimeix ? (o la seva etiqueta text=) sense número ni enllaç, la cel·la queda només amb text. inResource anomena el recurs en el peu, la nota o la cel·la del qual hi ha la referència. |
unknownDirective | Una línia :::nom el nom de la qual no és ni una directiva ni un contenidor. | La línia es compon com a text. |
malformedEmbed | Una línia ::nom que no és una inserció ben formada i aïllada: ::resource amb un id sense cometes o entre cometes simples o amb un altre atribut, o una línia enganxada sota un paràgraf sense línia en blanc. | La línia es compon com a text. |
fullwidthMarkup | Una línia porta marques escrites amb un mètode d'entrada xinès o japonès: una tanca :::, un encapçalament #, una crida de nota [^…], atributs {…} després d'una tanca o un encapçalament, o negreta **…**. typed és la marca tal com es va escriure i ascii, la forma que cal escriure. Un per línia. | La línia es compon com a text; no es converteix res. |
attributeKeyInvalid | Una clau d'atribut porta lletres fora de l'ASCII (作者=曹雪芹); assenyala la clau. | L'atribut s'ignora. |
unknownParagraphStyle | :::paragraphsstyle no anomena cap estil de paràgraf. | Els paràgrafs es componen com a text de cos. |
unknownCalloutType | :::callouttype no anomena cap dels calloutStyles; només es registra quan n'hi ha algun de configurat. | La caixa pren el primer estil d'avís. |
unknownChipStyle | :chip[…]style no anomena cap estil de xip. | El xip pren el primer estil de xip. |
undefinedFootnote | Una crida de nota [^id] que cap paràgraf [^id]: no defineix (id és el de la nota). | S'imprimeix el número; la nota queda buida. |
unusedFootnote | Una definició de nota [^id]: que cap crida no cita. | La nota no es compon. |
indexMarkInvalid | Una marca d'índex sense terme: :index, o atributs sense term en una marca sense text entre claudàtors. | La marca no indexa res. |
indexSeeUnknown | La destinació d'un see o un seealso (target) no és una entrada del seu índex (index, '' per al principal). Assenyala la línia :::index. | La remissió s'imprimeix igualment. |
indexRangeUnclosed | Una marca range="start" sense el seu range="end", o a l'inrevés (missing indica quin extrem falta; term, l'entrada). Assenyala la línia :::index. | L'interval imprimeix la seva única pàgina. |
unknownHeadingStyle | El style="…" d'un encapçalament no anomena cap estil d'encapçalament (level és el de l'encapçalament). | L'encapçalament i la seva secció conserven els ajustos propis del nivell. |
unknownTableStyle | El table.styleId d'un recurs de taula no anomena cap entrada de tableStyles. | La taula es compon amb tableStyle. |
raggedTableGrid | La quadrícula d'una taula no és rectangular un cop comptades les seves combinacions (vegeu Construir models de taula). | Les cel·les es desplacen sobre una combinació o deixen un buit. reason ('spanOverlap' / 'missingCells'), row i col situen el primer problema; count indica quants n'hi ha. |
Els avisos sobre un recurs —el seu estil de taula, la seva quadrícula, una referència dins del seu peu, la seva nota o les seves cel·les— apunten a la primera inserció o referència del recurs al text, i cadascun es registra una sola vegada per recurs. Només es comproven els recursos que fa servir el document: un capítol d'un llibre notifica les taules que cita, no totes les taules del llibre.
import { buildDocument, formatWarning } from 'postext';
const doc = buildDocument({ markdown: 'Vegeu :ref{id="fig-map"}.\n\n:::sidebar\nNotes.' }, config);
for (const w of [...(doc.warnings ?? []), ...(doc.contentWarnings ?? []), ...(doc.configWarnings ?? [])]) console.warn(formatWarning(w));
// Unknown resource id "fig-map" in :ref — it prints "?" (or its text= label), with no number or link (page 1, offset 6)
// Unknown directive ":::sidebar" — the line is set as text (page 1, offset 27)
// Estreny per `kind` per llegir els camps d'un tipus.
const missing = (doc.contentWarnings ?? []).flatMap((w) => (w.kind === 'unknownResourceId' ? [w.resourceId] : []));formatWarning(w) retorna una descripció en anglès d'una línia. Un amfitrió que localitza els seus missatges distingeix per kind —i conserva una branca per defecte, perquè les versions menors poden afegir tipus—. collectContentWarnings(markdown, config, resources) retorna els avisos de contingut sense maquetar res (la llista que afegeix la composició, sense pageIndex), per a un editor que revisa el text mentre s'escriu. collectHeadingDesignCuts(doc) revisa una maquetació acabada a la recerca de dissenys d'encapçalament amb un text que passa del peu de la seva pàgina o de la seva columna (kind: 'headingDesignCut'; vegeu Alçada reservada), que la maquetació mateixa no avisa, i formatWarning també en descriu els resultats. El tauler Revisió del Sandbox els mostra tots.
Els renderitzadors notifiquen el que no poden pintar com es demana mitjançant una opció onWarning: renderPageToCanvas, renderPage i renderToCanvas (RenderPageOptions), renderToHtml i renderToHtmlIndexed (RenderHtmlOptions), i renderToPdf (RenderToPdfOptions, també a través del worker de PDF). Avui hi ha un únic tipus d'avís de renderitzat, missingImage: una imatge —una figura, la imatge d'una cel·la de taula, la icona d'un avís, una imatge de disseny— sense res a dibuixar es pinta com un marcador de posició neutre i es notifica, una vegada per fileId i crida de renderitzat, amb el seu pageIndex, el resourceId quan el renderitzador el coneix (figures i imatges de cel·la) i, al PDF, el documentIndex d'un renderitzat de diversos documents. Res a dibuixar vol dir: cap registerResourceImage per al fileId al canvas, cap URL de resourceImageUrl en HTML, i cap byte de resourceBytes —o bytes que no es descodifiquen— al PDF. Un recurs de mapa de bits o SVG que no anomena cap fileId no té res a demanar: es dibuixa com a marcador de posició sense avís. Els avisos de renderitzat no es desen al VDT: el que un amfitrió pot aportar canvia després de la maquetació.
import { buildDocument, renderPage, type RenderWarning, type Resource } from 'postext';
const map: Resource = {
id: 'fig-map', typeId: 'figure', kind: 'bitmap', caption: 'La ruta.', createdAt: 0, updatedAt: 0,
bitmap: { fileId: 'map-file', format: 'png', width: 1200, height: 800 },
};
const doc = buildDocument({ markdown: 'Vegeu :ref{id="fig-map"}.', resources: [map] }, config);
const warnings: RenderWarning[] = [];
const canvas = renderPage(doc.pages[0], doc, { onWarning: (w) => warnings.push(w) });
// Fins que 'map-file' es registri amb registerResourceImage:
// [{ kind: 'missingImage', fileId: 'map-file', resourceId: 'fig-map', pageIndex: 0 }]#Renderitzar una pàgina a un bitmap
Cada pàgina es pot rasteritzar de manera independent. Fes servir renderPage(page, doc) per obtenir un HTMLCanvasElement a partir del número de pàgina — el canvas és un bitmap dimensionat exactament a la mida de la pàgina en píxels (al DPI configurat), de manera que el pots mostrar, exportar o passar a qualsevol pipeline d'imatge:
import { buildDocument, renderPage } from 'postext';
const vdt = buildDocument(content, config);
// Renderitzar la pàgina 3 (índex de base 0) com a bitmap
const pageNumber = 2;
const page = vdt.pages[pageNumber];
if (!page) throw new Error(`La pàgina ${pageNumber} no existeix`);
const canvas = renderPage(page, vdt);
// canvas.width / canvas.height són la mida del bitmap de la pàgina en píxels
// Mostrar-lo al DOM
document.body.appendChild(canvas);
// …o exportar-lo com a PNG data URL
const pngDataUrl = canvas.toDataURL('image/png');
// …o obtenir un Blob per baixar o pujar
canvas.toBlob((blob) => {
if (blob) saveAs(blob, `pagina-${pageNumber + 1}.png`);
}, 'image/png');
// …o accedir als píxels RGBA en brut
const ctx = canvas.getContext('2d')!;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);Si prefereixes pintar sobre un canvas que ja tens (per exemple un de muntat al DOM amb una disposició concreta), fes servir renderPageToCanvas(page, doc, canvas) — redimensiona i dibuixa al canvas que li passis en lloc de crear-ne un de nou.
Per renderitzar totes les pàgines, itera sobre vdt.pages:
const bitmaps = vdt.pages.map((page) => renderPage(page, vdt));Exemple en viu: una pàgina com a imatge
Tot l'anterior, executant-se al navegador. El pen importa l'última versió publicada de postext des d'un CDN, espera les fonts web, compon un document curt a dues columnes, pinta la primera pàgina en un canvas i ofereix aquest bitmap com a PNG. Prem Executar a CodePen per carregar l'editor i canviar el markdown o la configuració; la pàgina es torna a pintar amb cada edició.
import { buildDocument, renderPage } from 'https://esm.sh/postext';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
// 150 dpi: crisp enough for a preview, light enough to paint instantly.
page: { sizePreset: '17x24', dpi: 150 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
// The whole layout: one entry per page in doc.pages, with exact coordinates.
const doc = buildDocument({ markdown }, config);
// Rasterise the first page. The canvas is sized to the page at the configured dpi.
const canvas = renderPage(doc.pages[0], doc);
document.getElementById('page').replaceChildren(canvas);
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · page 1 is ${canvas.width} × ${canvas.height} px`;
// The same bitmap as a PNG file.
canvas.toBlob((blob) => {
const link = document.getElementById('download');
link.href = URL.createObjectURL(blob);
link.hidden = false;
}, 'image/png');index.html
<p id="status">Laying out…</p>
<a id="download" download="page-1.png" hidden>Download page 1 as PNG</a>
<div id="page"></div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#page canvas {
display: block;
max-width: 100%;
height: auto;
margin-top: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#React
postext/react exporta createLayout(content, config?): un component que compon el document una sola vegada, en muntar-se, i mostra cada pàgina com un <canvas> dins d'un <div>.
import { createLayout } from 'postext/react';
const Article = createLayout(
{ markdown: '# Hola\n\nEl primer paràgraf de l\'article.' },
{ page: { sizePreset: '17x24' } },
);
export function ArticlePage() {
return <Article className="pages" style={{ maxWidth: 480 }} />;
}- Al fil principal, una vegada. Les pàgines es pinten a la resolució del document i s'escalen a l'amplada del contenidor.
contenticonfigqueden fixats en cridarcreateLayout; crea un altre component per mostrar una altra cosa. Per a una previsualització en viu, compon al Web Worker i pinta ambrenderPageToCanvas, com a l'exemple de React d'aquella secció. - Primer, fonts i imatges. Carrega les fonts web del document abans que el component es munti i registra'n les imatges amb
registerResourceImage. Si el markdown conté un$, el component arrenca el motor de fórmules pel seu compte. - React es queda fora de l'entrada principal.
postextno importa mai React; només ho fapostext/react.createLayoutcontinua exportant-se des depostextperquè el codi existent continuï funcionant, però està obsolet: carregapostext/reactquan el crides, i el component queda suspès fins que arriba (React el torna a renderitzar tot sol). Importa'l des depostext/react. - El component obsolet se suspèn. Fins que arriba
postext/react, elcreateLayoutdepostextnecessita una arrel concurrent (createRoot) o un límit<Suspense>per sobre. En una arrel heretada deReactDOM.render, o arenderToString, sense aquest límit, React informa d'un error.reactcontinua sent una peer dependency obligatòria, perquè els bundlers puguin resoldre aquesta importació diferida.
#Resoldre valors per defecte
Les funcions de resolució omplen els valors per defecte per a objectes de configuració parcials. Això és útil quan necessites una configuració completa per a inspecció o comparació:
import { resolvePageConfig, resolveBodyTextConfig } from 'postext';
const fullPage = resolvePageConfig({ sizePreset: '21x28' });
// => { sizePreset: '21x28', width: { value: 21, unit: 'cm' }, height: { value: 28, unit: 'cm' },
// margins: { top: { value: 2, unit: 'cm' }, ... }, dpi: 300, cutLines: { enabled: false, ... }, ... }
const fullBody = resolveBodyTextConfig({ fontFamily: 'Inter' });
// => { fontFamily: 'Inter', fontSize: { value: 8, unit: 'pt' }, lineHeight: { value: 1.5, unit: 'em' }, ... }Resolvers disponibles, un per secció de primer nivell: resolvePageConfig, resolveLayoutConfig, resolveBodyTextConfig, resolveHeadingsConfig, resolveHeadingStylesConfig, resolveTocConfig, resolvePartsConfig, resolveUnorderedListsConfig, resolveOrderedListsConfig, resolveMathConfig, resolveTableStyleConfig, resolveCaptionStyleConfig, resolveDiagramStyleConfig, resolveParagraphStylesConfig, resolveCalloutStylesConfig, resolveHeaderFooterConfig, resolveDebugConfig, resolveHtmlViewerConfig, resolvePdfGenerationConfig — més resolveDesignSlot per a una sola ranura de disseny. Les paletes de color s'apliquen per separat amb applyPaletteToConfig(config), applyPaletteToResolvedConfig(resolved, palette) i resolveColorValue(value, palette, fallback) — vegeu Paleta de colors.
Els resolvers amb valors per defecte que hereten d'una altra secció reben aquesta secció, ja resolta, com a argument addicional. resolveUnorderedListsConfig i resolveOrderedListsConfig reben el cos de text resolt, perquè les llistes n'hereten fontFamily i color; resolveCalloutStylesConfig rep el cos de text, els encapçalaments i les llistes no ordenades resolts (vegeu l'exemple a Estils d'avís), i resolveHeadingStylesConfig la pàgina, el cos de text i les dues seccions de llistes resoltes. Consulta les declaracions de tipus del paquet per a la signatura exacta de cadascun:
import { resolveBodyTextConfig, resolveUnorderedListsConfig } from 'postext';
const body = resolveBodyTextConfig({ fontFamily: 'Inter' });
const lists = resolveUnorderedListsConfig({ bulletChar: '—' }, body);
// => lists.fontFamily === 'Inter' (heretat)També s'exporten els paquets de valors per defecte estàtics — els que es fan servir quan no hi ha herència: DEFAULT_PAGE_CONFIG, DEFAULT_CUT_LINES, DEFAULT_PAGE_NUMBERING, PAGE_SIZE_PRESETS, DEFAULT_LAYOUT_CONFIG, DEFAULT_COLUMN_RULE, DEFAULT_COLUMN_BALANCING, DEFAULT_BODY_TEXT_CONFIG, DEFAULT_HYPHENATION_CONFIG, DEFAULT_HEADINGS_CONFIG, DEFAULT_UNORDERED_LISTS_STATIC, DEFAULT_ORDERED_LISTS_STATIC, DEFAULT_PARAGRAPH_STYLES, DEFAULT_CALLOUT_STYLES, DEFAULT_CALLOUT_STYLE_STATIC, DEFAULT_PARTS_CONFIG, DEFAULT_HEADING_STYLES, DEFAULT_TOC_CONFIG, DEFAULT_MATH_CONFIG, DEFAULT_DIAGRAM_STYLE_CONFIG, DEFAULT_DEBUG_CONFIG, DEFAULT_HTML_VIEWER_CONFIG, DEFAULT_PDF_GENERATION_CONFIG, DEFAULT_COLOR_PALETTE, DEFAULT_MAIN_COLOR, DEFAULT_MAIN_COLOR_ID, DEFAULT_MAIN_COLOR_NAME, DEFAULT_MAIN_COLOR_HEX, a més dels valors per defecte dels elements de capçalera/peu (DEFAULT_HEADER_FOOTER_SLOT, DEFAULT_HEADER_SLOT, DEFAULT_FOOTER_SLOT, DEFAULT_TEXT_ELEMENT, DEFAULT_RULE_ELEMENT, DEFAULT_BOX_ELEMENT) i la funció sensible a la llengua defaultResourceTypes(locale) (vegeu Tipus de recurs).
#Eliminar valors per defecte
En persistir la configuració (per exemple, a localStorage o un fitxer), fes servir stripConfigDefaults per eliminar els valors que coincideixen amb els valors per defecte. Així les configuracions emmagatzemades es mantenen mínimes — només es desen les modificacions intencionades:
import { stripConfigDefaults } from 'postext';
const minimal = stripConfigDefaults(fullConfig);
// Només hi romanen les propietats que difereixen dels valors per defecteTambé hi ha disponibles funcions individuals, una per resolver: stripPageDefaults, stripLayoutDefaults, stripBodyTextDefaults, stripHeadingsDefaults, stripHeadingStylesDefaults, stripTocDefaults, stripPartsDefaults, stripUnorderedListsDefaults, stripOrderedListsDefaults, stripMathDefaults, stripTableStyleDefaults, stripCaptionStyleDefaults, stripDiagramStyleDefaults, stripParagraphStylesDefaults, stripCalloutStylesDefaults, stripHeaderFooterDefaults, stripDesignSlotDefaults, stripDebugDefaults, stripHtmlViewerDefaults, stripPdfGenerationDefaults.
#Anàlisi
El motor exposa el seu tokenitzador de markdown i el seu lector de frontmatter. Fes-los servir per inspeccionar un document abans de construir-lo, o per alimentar altres eines amb la mateixa estructura de blocs que veu Postext:
import { parseMarkdown, extractFrontmatter } from 'postext';
const source = '---\ntitle: Capítol u\n---\n\n# Obertura\n\nLa història comença aquí.';
const { metadata, content } = extractFrontmatter(source);
// metadata.title === 'Capítol u'
const blocks = parseMarkdown(content);
// => [ { type: 'heading', level: 1, text: 'Obertura', … },
// { type: 'paragraph', text: 'La història comença aquí.', … } ]Consulta la pàgina de Format del document per veure la llista completa de construccions markdown que reconeix Postext.
#Memòria cau de mesures
La mesura del text és el pas costós del procés de composició. Dues menes de memòria cau l'abarateixen, i es buiden de manera diferent:
- Una memòria cau de blocs que és teva.
createMeasurementCache()retorna unaMeasurementCacheque recorda cada paràgraf mesurat, amb el seu text, les seves fonts, la seva amplada, les seves opcions de tall de línia i el diccionari de partició de mots actiu com a clau. Passa-la com a tercer argument debuildDocument(o debuildDocumentAsync) per reutilitzar les mesures entre les passades de convergència i entre composicions: un editor que recompon el document a cada pulsació mesura llavors només els paràgrafs que han canviat. Sense ella, cada passada torna a mesurar tots els blocs. Un paràgraf llegit de la memòria cau és igual que un de mesurat de nou, de manera que una composició amb memòria cau talla cada línia com una sense; a postext 1.4.1 un paràgraf desat a la memòria cau perdia la marca d'una última línia massa curta, i l'ajust d'aquestes línies i l'equilibrat de columnes podien tallar-lo d'una altra manera. El worker de composició en conserva una entre composicions i la substitueix quan canvien les fonts. - Memòries cau globals d'amplades. Les amplades de paraula es desen per cadena de font en un estat de mòdul que comparteixen totes les composicions de la pàgina, i pretext té la seva pròpia memòria cau. Són les que queden obsoletes quan una font web arriba després d'haver mesurat el text amb una font de reserva.
import { buildDocument, createMeasurementCache, clearMeasurementCache } from 'postext';
import type { MeasurementCache } from 'postext';
let cache: MeasurementCache = createMeasurementCache();
let doc = buildDocument(content, config, cache);
// Ha acabat de carregar una font web: les amplades mesurades amb la de reserva no serveixen.
await document.fonts.ready;
clearMeasurementCache(); // sense argument: buida les memòries cau globals d'amplades
cache = createMeasurementCache(); // una memòria cau de blocs no té clear(); crea'n una altra
doc = buildDocument(content, config, cache);clearMeasurementCache() no rep arguments ni toca cap MeasurementCache: els seus blocs també es van mesurar amb les amplades velles, així que descarta-la i crea'n una de nova. Recompondre després de carregar una font sense buidar les memòries cau dona els mateixos talls de línia, mesurats amb la font de reserva.
Per a aplicacions que mesuren el text peça a peça, cachedMeasureBlock(text, font, maxWidthPx, lineHeightPx, options, cache) i cachedMeasureRichBlock(spans, normalFont, boldFont, italicFont, boldItalicFont, maxWidthPx, lineHeightPx, options, cache) reben els arguments de measureBlock i measureRichBlock més la memòria cau, al final.
#Estat global compartit en una pàgina
Part de l'estat de Postext viu en variables de mòdul. Tot el que importa postext en el mateix context de JavaScript (realm) el comparteix: una pàgina i els seus scripts comparteixen una còpia, mentre que cada iframe i cada worker tenen la seva. Amb un document per pàgina no es nota. Amb diversos documents a la mateixa pàgina —dues previsualitzacions en directe, una galeria d'exemples— sí:
- Imatges dels recursos.
registerResourceImage(fileId, image)omple un únic registre indexat perfileId, que llegeixenrenderPageirenderPageToCanvas. Dos documents que registrenfigure.svgcomparteixen aquesta entrada: guanya l'últim registre, per a tots dos. Posa als identificadors de fitxer un prefix per document, i cridaunregisterResourceImage(fileId)oclearResourceImages()quan un document desapareix. Els ràsters que desa a la memòria cau el backend de canvas fan servir la mateixa clau i es descarten amb la imatge. - Mesures del text. Les amplades mesurades es desen a la memòria cau per cadena de font i text per a tot el context. El text mesurat abans que una font web acabés de carregar-se conserva les amplades de la font de reserva, en tots els documents, fins que
clearMeasurementCache()(sense arguments) buida les memòries cau: crida-la quan les fonts s'hagin carregat i torna a compondre. - Configuracions resoltes. Cada objecte de configuració es resol una vegada i el resultat queda a la memòria cau associat a aquest objecte. Una configuració modificada al lloc i composta de nou es compon amb els valors antics: passa un objecte nou a cada canvi (
{ ...config, … }ostructuredClone(config)). - Llengua de la partició de mots. Cada build fixa la llengua de partició de mots de tot el procés al
bodyText.hyphenation.localedel seu document. Les funcions exportadeshyphenateText(text)ilayoutDesignSlotfan servir la llengua de l'últim build tret que els en passis una: cridahyphenateText(text, 'es'). - Motor de fórmules. Hi ha un únic motor MathJax i una única memòria cau de fórmules renderitzades per context;
initMathEngine()l'engega per a tots.
L'aïllament més senzill és un context per document: un iframe per exemple en directe (una inserció de CodePen ho és), o un worker de composició per document per a les mesures i la partició de mots (les imatges es continuen registrant a la pàgina).
#Executar la composició en un Web Worker
Aquesta és la manera recomanada de fer servir Postext al navegador. Si construeixes alguna cosa interactiva — una previsualització en directe, un editor, un visor sensible al resize o un playground a l'estil del sandbox — dirigeix el pipeline a través de createLayoutWorker() de postext/worker. No cridis buildDocument directament al fil principal per a codi d'UI.
Cridar buildDocument al fil principal executa el pipeline complet — anàlisi, mesura, set passades, fins a cinc iteracions de convergència — al fil que invoca la funció. Per a una exportació puntual està bé. Per a una interfície interactiva és el fil equivocat: una composició de 150 ms bloqueja els esdeveniments d'entrada, les pulsacions de teclat s'encuen i el desplaçament va a batzegades. El worker trasllada cadascun d'aquests mil·lisegons a un fil secundari.
Postext inclou un punt d'entrada dedicat a Web Worker — postext/worker — que treu el pipeline del fil principal. És el camí que esperem que segueixin la majoria de les integracions: les tres vistes del sandbox (Canvas, HTML i PDF) comparteixen el mateix handle createLayoutWorker() a través d'un únic hook useLayoutWorker (packages/postext-sandbox/src/worker/useLayoutWorker.ts) i el dirigeixen amb cancel·lació last-wins — una nova pulsació avorta el build en curs fins i tot abans que acabi.
D'un cop d'ull, la integració canònica és:
- Crea un worker una vegada per vista amb
createLayoutWorker(). - Registra les fonts una vegada per família enviant
ArrayBuffers transferibles viaregisterFonts(payloads). - Compon amb
build(content, config, { signal }), passant unAbortSignalnou a cada crida per poder cancel·lar builds obsolets. - Substitueix qualsevol build anterior avortant-ne el signal abans d'iniciar el següent — aquest és el patró last-wins.
- Allibera (dispose) el worker quan el component propietari es desmunta.
El mateix VDTDocument que retorna build(...) alimenta cada renderitzador posterior: renderPage/renderPageToCanvas per a canvas, renderToHtmlIndexed per a HTML i renderToPdf (de postext-pdf) per a PDF. Construeixes una vegada al worker i rasteritzes tantes vegades com necessiti la UI al fil principal.
#Què t'aporta el worker
- El fil principal queda lliure. L'anàlisi, la mesura i el bucle de convergència de set passades s'executen tots dins del worker. El fil principal només es toca quan es publica de tornada el
VDTDocumentacabat. - Cancel·lació last-wins.
build(content, config, { signal })injecta unAbortSignalal worker. Avortar abans que acabi produeix unAbortErroral costat principal; dins del worker el pipeline llança unBuildCancelledErroral següent punt de comprovació per bloc i s'atura immediatament. - Memòria cau de mesura per worker. El worker manté una única
MeasurementCachedurant tota la seva vida. Els builds posteriors que comparteixen font, text i amplada reutilitzen les mesures desades — escriure un sol caràcter en un document llarg només torna a mesurar els blocs l'entrada dels quals ha canviat realment. - Mètriques idèntiques al fil principal. Les fonts s'envien al worker com a
ArrayBuffers transferibles i es registren mitjançantnew FontFace(...)alFontFaceSetpropi del worker. Les mesures fan servir les mateixes mètriques de font del canvas que faria servir el fil principal, de manera que els talls de línia i les alçades de columna són idèntics byte a byte. - La memòria cau de rasterització matemàtica sobreviu als builds. El renderitzador matemàtic inclou una memòria cau de ràsters amb clau per contingut al costat de la d'identitat — clonar estructuralment un
MathRendera través de la frontera del worker fallaria si només tinguéssim la memòria cau per identitat.
#API pública
El client del worker viu al subpath postext/worker i és un petit grapat de noms:
createLayoutWorker(opts?): LayoutWorkerHandle— instancia un worker dedicat (o n'embolcalla un que passis mitjançantopts.worker, o engega l'entrada del worker que hi ha aopts.url) i retorna un handle tipat. Consulta Carregar el worker des d'una CDN.LayoutWorkerHandle.registerFonts(faces: FontPayload[]): Promise<void>— envia bytes de font al worker. Els buffers es transfereixen, així que desa'n una còpia nova al fil principal si després els necessites.LayoutWorkerHandle.build(content, config?, { signal? }): Promise<VDTDocument>— executa el pipeline. Avortar el senyal cancel·la el build en curs.LayoutWorkerHandle.dispose(): void— acaba el worker i rebutja qualsevol build pendent ambAbortError.FontPayload—{ family, weight, style, unicodeRange?, buffer: ArrayBuffer }.weightés un pes CSS en forma de cadena ('700','bold');registerFontstambé l'accepta com a nombre (700). Elbufferes transfereix al worker quan cridesregisterFonts.BuildCancelledError(reexportat des depostext) — el que llançabuildDocumentinternament quanoptions.shouldCancelretornatrue. Normalment no el veus al fil principal: el protocol del worker el converteix en unAbortErrorabans d'arribar al teu codi.
El paquet també publica el path postext/worker/entry, que apunta a l'script compilat del worker. createLayoutWorker() resol aquesta URL automàticament; només cal que la referenciïs explícitament quan el teu bundler exigeix una crida manual a new Worker(new URL(...), { type: 'module' }), o quan serveixes l'entrada tu mateix (opts.url).
#Integració mínima
import { createLayoutWorker } from 'postext/worker';
import type { FontPayload, LayoutWorkerHandle } from 'postext/worker';
import type { PostextConfig, VDTDocument } from 'postext';
// 1. Crea el worker una sola vegada i conserva el handle durant tota la vida de la teva vista.
const layout: LayoutWorkerHandle = createLayoutWorker();
// 2. Registra les fonts una vegada per família (ArrayBuffers transferibles).
// getConfigFontFamilies(config) és un helper que llista les famílies que la teva config renderitzarà.
const payloads: FontPayload[] = await collectFontPayloadsForFamilies([
'EB Garamond',
'Open Sans',
]);
await layout.registerFonts(payloads);
// 3. Dirigeix els builds amb cancel·lació last-wins: avorta el signal anterior
// abans d'iniciar-ne un de nou. Un build obsolet es descarta dins del worker.
let pending: AbortController | null = null;
async function rebuild(
markdown: string,
config: PostextConfig,
): Promise<VDTDocument | null> {
pending?.abort();
pending = new AbortController();
try {
return await layout.build({ markdown }, config, { signal: pending.signal });
} catch (err) {
if ((err as { name?: string } | null)?.name === 'AbortError') return null;
throw err;
}
}
// 4. Allibera (dispose) quan el component propietari del worker es desmunta.
// Els builds pendents es rebutgen amb AbortError.
layout.dispose();Embolcallat en un component React, la forma és:
import { useEffect, useRef } from 'react';
import { createLayoutWorker } from 'postext/worker';
import type { LayoutWorkerHandle } from 'postext/worker';
import { renderPageToCanvas } from 'postext';
import type { PostextConfig } from 'postext';
export function CanvasPreview({
markdown,
config,
}: {
markdown: string;
config: PostextConfig;
}) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const workerRef = useRef<LayoutWorkerHandle | null>(null);
const pendingRef = useRef<AbortController | null>(null);
// Muntatge: engega el worker i envia les fonts una sola vegada.
useEffect(() => {
const handle = createLayoutWorker();
workerRef.current = handle;
(async () => {
const payloads = await collectFontPayloadsForFamilies(
getConfigFontFamilies(config),
);
await handle.registerFonts(payloads);
})();
return () => {
pendingRef.current?.abort();
handle.dispose();
};
}, []); // les fonts es registren una vegada; torna-les a registrar només quan canviï el conjunt de famílies
// A cada tecla o canvi de config: substitueix el build en curs i llança'n un de nou.
useEffect(() => {
const handle = workerRef.current;
if (!handle) return;
pendingRef.current?.abort();
const ac = new AbortController();
pendingRef.current = ac;
(async () => {
try {
const vdt = await handle.build({ markdown }, config, { signal: ac.signal });
const canvas = canvasRef.current;
if (!canvas || !vdt.pages[0]) return;
renderPageToCanvas(vdt.pages[0], vdt, canvas); // rasteritza al fil principal
} catch (err) {
if ((err as { name?: string } | null)?.name !== 'AbortError') throw err;
}
})();
}, [markdown, config]);
return <canvas ref={canvasRef} />;
}El patró sempre és el mateix: crea una vegada, registra les fonts una vegada, construeix-amb-AbortSignal moltes vegades, allibera en desmuntar.
#Carregar el worker des d'una CDN
L'script d'un worker ha de venir del mateix origen que la pàgina, així que una còpia de postext/worker servida per una CDN no pot engegar el fitxer layout.worker.js que té al costat. createLayoutWorker() ho resol:
- esm.sh, sense opcions. Quan
postext/workermateix s'ha carregat des d'esm.sh (la URL del seu mòdul té la formahttps://esm.sh/postext@1.5.0/es2022/worker.mjs), el client engega l'entrada corresponent,https://esm.sh/postext@1.5.0/worker/entry, a través d'un mòdul blob d'una línia, del mateix origen, que la importa. El mateix val per a les importacions que afegeixen?deps=,?external=o?alias=, i per a la formahttps://esm.sh/*postext@1.5.0/worker. El worker rep sempre la compilació normal d'aquesta versió, perquè un worker no té import map amb què resoldre dependències externes. - Qualsevol altre servidor, amb
url. Les altres CDN, com jsDelivr (/+esm) o unpkg, no es detecten.createLayoutWorker({ url })engega el mòdul d'entrada del worker que hi ha aurl: directament si és del mateix origen, i a través del mateix embolcall blob si és d'un altre. Aquest servidor ha de permetre peticions d'altres orígens (CORS). - Amb un bundler no canvia res. Amb Vite, webpack o Next.js, continua cridant
createLayoutWorker()sense opcions: ells emeten el worker com un fragment més de la teva aplicació.
import { createLayoutWorker } from 'https://esm.sh/postext/worker';
const layout = createLayoutWorker();
const face = async (weight, style) => ({
family: 'EB Garamond',
weight,
style,
buffer: await (await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/eb-garamond@5/files/eb-garamond-latin-${weight}-${style}.woff2`)).arrayBuffer(),
});
await layout.registerFonts(await Promise.all([face(400, 'normal'), face(700, 'normal'), face(400, 'italic')]));
const doc = await layout.build({ markdown }, { bodyText: { fontFamily: 'EB Garamond' } });Un worker no veu les fonts de la pàgina. Té el seu propi conjunt de fonts, que només conté les cares enviades amb registerFonts i les fonts instal·lades al sistema. Quan un build compon text en una família que el worker no troba, aquest text es mesura amb una font de reserva, així que els seus talls de línia no coincidiran amb els de la pàgina. El client imprimeix llavors un avís a la consola per família ("EB Garamond" is not available inside the layout worker…) i enumera les famílies a BuildStats.missingFonts, que rep el callback onStats de build.
#Recol·lecció de payloads de font (Fontsource / Google Fonts)
registerFonts pren bytes de font en brut. El fil principal és el lloc adequat per obtenir-los, perquè Google Fonts només retorna WOFF2 a User-Agents amb aspecte de navegador, i perquè una memòria cau centralitzada permet que diverses instàncies del worker comparteixin els mateixos bytes.
collectFontPayloadsForFamilies del sandbox (packages/postext-sandbox/src/controls/fontLoader.ts) és una implementació de referència directa. El que fa:
- Consulta
https://api.fontsource.org/v1/fonts/{id-de-familia}per descobrir els pesos disponibles i si la família inclou un eix variable. - Construeix una URL CSS2 de Google Fonts que cobreix tots els pesos i estils que declara la família.
- Baixa el full d'estils
@font-facegenerat, n'extreu cada declaraciósrc: url(...) format('woff2')i baixa els bytes en brut. - Retorna un
FontPayload[]onbufferés unArrayBuffernou per crida — important, perquèregisterFontstransfereix el buffer i deixa la còpia del remitent desvinculada.
Combina-ho amb getConfigFontFamilies(config) per obtenir la llista de famílies que una PostextConfig concreta renderitzarà (cos, encapçalaments, pics de llistes, números de llistes ordenades).
#Cancel·lació cooperativa dins del motor
Si orquestres buildDocument tu mateix — per exemple, dins d'un worker personalitzat — el pipeline exposa un hook shouldCancel que pots fer servir directament:
import { buildDocument, BuildCancelledError } from 'postext';
let superseded = false;
try {
const vdt = buildDocument(content, config, cache, {
shouldCancel: () => superseded,
});
} catch (err) {
if (err instanceof BuildCancelledError) return; // un build més nou ha pres el relleu
throw err;
}shouldCancel s'invoca una vegada per cada bloc de nivell superior durant la col·locació. El hook és intencionadament cooperatiu — no pot aturar la crida de layout de Pretext mateixa a mitja línia, però manté la granularitat de cancel·lació prou fina (mil·lisegons) perquè un usuari que tecleja de pressa no hagi d'esperar mai un build obsolet.
#Exportar PDF des del worker
El backend PDF pren un VDTDocument ja enllestit i el converteix en bytes PDF. No torna a executar la composició. Això vol dir que el flux canònic de PDF al navegador encaixa netament amb el worker: construeix el VDT al worker (fora del fil principal, cancel·lable, reutilitzant la memòria cau), i després crida renderToPdf al fil principal sobre aquest mateix VDT.
import type { LayoutWorkerHandle } from 'postext/worker';
import { renderToPdf } from 'postext-pdf';
import type { PostextConfig } from 'postext';
import { createPdfFontProvider } from './pdfFontProvider';
const fontProvider = createPdfFontProvider();
export async function exportPdf(
layout: LayoutWorkerHandle,
markdown: string,
config: PostextConfig,
): Promise<Uint8Array> {
// 1. Construeix el VDT al worker — la UI continua responent durant les passades de composició.
const vdt = await layout.build({ markdown }, config);
// 2. Rasteritza a PDF al fil principal. renderToPdf és ràpid un cop existeix el VDT
// perquè recorre coordenades precalculades, no torna a mesurar text.
return renderToPdf(vdt, {
fontProvider,
// La config `pdfGeneration` de `vdt.config` es respecta automàticament.
});
}Si ja mantens un handle de worker per a la previsualització en directe, reutilitza'l per a l'exportació en comptes d'engegar un segon worker — la memòria cau de mesura dins del worker fa que una exportació PDF posterior a una previsualització en pantalla sigui pràcticament gratuïta.
En un llibre llarg, escriure el PDF mateix també porta segons; postext-pdf/worker executa aquest pas en un worker propi (consulta Renderitzar el PDF en un worker).
#Quan fer servir el worker i quan no
Fes servir el worker per a:
- Previsualitzacions en directe, editors i playgrounds. Qualsevol escenari on el document es reconstrueix en resposta a l'entrada de l'usuari.
- Visors HTML sensibles al resize que tornen a executar la composició a cada tic del
ResizeObserver. - Exportació PDF des del navegador llançada des d'una UI que ja té previsualització en directe — reutilitza el handle de worker existent per aprofitar la memòria cau de mesura.
- Múltiples pestanyes de sortida que necessiten el mateix VDT (les vistes Canvas / HTML / PDF del sandbox comparteixen un handle de worker per muntatge de vista).
Salta-te'l per a:
- Generació al servidor — Node no té un
FontFaceSetdel navegador, i de tota manera controles el fil. - Exportacions puntuals aïllades (una CLI, un script d'exportació headless, una Cloud Function) en què no existeix cap UI interactiva que es pugui bloquejar. Cridar
buildDocumentdirectament és més simple i evita el cost de la transferència inicial de fonts.
#Integrar el visor HTML
El visor HTML és el renderitzador de Postext orientat a pantalla. En lloc de rasteritzar pàgines a un mapa de bits emet nodes DOM posicionats absolutament la geometria dels quals genera el mateix pipeline que produeix la sortida impresa. És l'opció adequada quan vols tipografia llegible, seleccionable i conscient del resize al navegador — una app de lectura, una previsualització dins d'un producte o una superfície de documentació incrustada — sense haver d'arrossegar un visor PDF.
Les peces clau de l'API pública:
buildDocument(content, config, cache?)— executa el pipeline complet de composició i retorna unVDTDocument.renderToHtmlIndexed(doc, options)— converteix el VDT en una única cadena HTML més un desglossament per pàgina i per bloc. El desglossament permet apedaçar el DOM de manera barata quan només han canviat alguns blocs entre renders.resolveHtmlViewerConfig(partial)— completa els valors per defecte del visor HTML (maxCharsPerLine,columnGap,optimalLineBreaking).buildFontString+measureGlyphWidth+dimensionToPx— primitives de mesura que serveixen per derivar una amplada de columna real en píxels a partir d'un objectiu en caràcters.createMeasurementCache/clearMeasurementCache— memòries cau endollables per reutilitzar mesures entre recomposicions.
#Exemple en directe: una cadena HTML
El recorregut complet en JavaScript pla, abans de la integració amb React de més avall: construir el document, passar el VDTDocument a renderToHtml i bolcar la cadena en un contenidor. mode: 'single' apila les pàgines en vertical; background els dona color, perquè per defecte les pàgines són transparents. El pen també imprimeix el marcatge generat, perquè vegis les línies posicionades de manera absoluta que emet el renderitzador: el navegador les pinta, però no les recompon mai.
import { buildDocument, renderToHtml } from 'https://esm.sh/postext';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
// 96 dpi: page pixels are CSS pixels, so the HTML shows at its real size.
page: { sizePreset: '17x24', dpi: 96 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
// One HTML string for the whole document. Every line is an absolutely
// positioned element, so the browser never reflows the text.
const html = renderToHtml(doc, { mode: 'single', background: '#ffffff' });
document.getElementById('viewer').innerHTML = html;
document.getElementById('source').textContent = html;
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · ${(html.length / 1024).toFixed(1)} KB of HTML`;index.html
<p id="status">Laying out…</p>
<div id="viewer"></div>
<details>
<summary>Generated HTML</summary>
<pre id="source"></pre>
</details>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
/* The page is wider than this pane: let it scroll instead of clipping it.
The renderer centres pages with an inline style, hence the !important. */
#viewer {
overflow: auto;
}
#viewer .pt-doc {
align-items: flex-start !important;
}
/* Each page is a .pt-page block; the renderer positions every line inside it. */
#viewer .pt-page {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
details {
margin-top: 16px;
}
#source {
max-height: 240px;
overflow: auto;
padding: 8px;
background: #fff;
font-size: 11px;
white-space: pre-wrap;
word-break: break-all;
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Integració mínima
El fragment següent és la integració útil més curta: construeix el document a la mida actual de la vista, el renderitza en un contenidor i recompon en redimensionar.
import { useEffect, useRef } from 'react';
import {
buildDocument,
renderToHtmlIndexed,
resolveHtmlViewerConfig,
buildFontString,
measureGlyphWidth,
dimensionToPx,
createMeasurementCache,
} from 'postext';
import type { PostextConfig, MeasurementCache } from 'postext';
// DPI adequat per a pantalla: a 144 DPI una mida de cos de 8pt resol a 16 px.
const HTML_DPI = 144;
const PADDING_PX = 24;
// Mostra de prosa que serveix per mesurar l'amplada objectiu de columna. Les fonts
// proporcionals fan poc fiable "N × amplada mitjana", així que mesurem una
// cadena representativa.
const SAMPLE =
'The quick brown fox jumps over the lazy dog. Sphinx of black quartz, judge my vow.';
function sampleForChars(n: number): string {
let s = SAMPLE;
while (s.length < n) s += ' ' + SAMPLE;
return s.slice(0, n);
}
export function PostextHtmlViewer({
markdown,
config,
mode = 'multi',
}: {
markdown: string;
config: PostextConfig;
mode?: 'single' | 'multi';
}) {
const hostRef = useRef<HTMLDivElement | null>(null);
const cacheRef = useRef<MeasurementCache>(createMeasurementCache());
useEffect(() => {
const host = hostRef.current;
if (!host) return;
const relayout = () => {
const rect = host.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const viewer = resolveHtmlViewerConfig(config.htmlViewer);
const fontFamily = config.bodyText?.fontFamily ?? 'EB Garamond';
const fontWeight = config.bodyText?.fontWeight ?? 400;
const fontSize = config.bodyText?.fontSize ?? { value: 8, unit: 'pt' as const };
const fontSizePx = dimensionToPx(fontSize, HTML_DPI);
// Mesura l'amplada *real* de columna per a N caràcters de prosa del cos.
const targetColumnPx = measureGlyphWidth(
sampleForChars(viewer.maxCharsPerLine),
buildFontString(fontFamily, fontSizePx, String(fontWeight), 'normal'),
);
const inner = Math.max(rect.width - PADDING_PX * 2, 100);
let columnWidthPx: number;
if (mode === 'single') {
columnWidthPx = Math.min(targetColumnPx, inner);
} else {
// Hi encabeix tantes columnes com es pugui a l'amplada objectiu.
const count = Math.max(
1,
Math.floor((inner + viewer.columnGap) / (targetColumnPx + viewer.columnGap)),
);
columnWidthPx = (inner - viewer.columnGap * (count - 1)) / count;
}
columnWidthPx = Math.max(Math.floor(columnWidthPx), 80);
// El mode single fa servir una pàgina molt alta; el mode multi fa servir l'alçada
// de la vista, de manera que cada "pàgina" VDT es converteix en una columna.
const pageHeightPx =
mode === 'single' ? Math.max(rect.height * 20, 200_000) : Math.max(rect.height - PADDING_PX * 2, 400);
const override: PostextConfig = {
...config,
page: {
...config.page,
dpi: HTML_DPI,
width: { value: columnWidthPx, unit: 'px' },
height: { value: pageHeightPx, unit: 'px' },
margins: {
top: { value: 0, unit: 'px' },
bottom: { value: 0, unit: 'px' },
left: { value: 0, unit: 'px' },
right: { value: 0, unit: 'px' },
},
},
layout: { ...config.layout, layoutType: 'single' },
bodyText: {
...config.bodyText,
optimalLineBreaking: viewer.optimalLineBreaking,
},
};
const doc = buildDocument({ markdown }, override, cacheRef.current);
const { html } = renderToHtmlIndexed(doc, {
mode,
columnGap: viewer.columnGap,
padding: PADDING_PX,
background: 'transparent',
});
host.innerHTML = html;
};
relayout();
const ro = new ResizeObserver(() => relayout());
ro.observe(host);
// Torna a mesurar quan es carreguen les web fonts perquè les amplades de glif no
// quedin preses a partir de les fonts de reserva.
const onFontsDone = () => relayout();
document.fonts?.addEventListener?.('loadingdone', onFontsDone);
return () => {
ro.disconnect();
document.fonts?.removeEventListener?.('loadingdone', onFontsDone);
};
}, [markdown, config, mode]);
return <div ref={hostRef} style={{ width: '100%', height: '100%', overflow: 'auto' }} />;
}Algunes notes sobre el que fa l'exemple:
- Es mesura la columna, no s'aproxima. Com que
maxCharsPerLineés un objectiu expressat en caràcters, l'amplada real en píxels depèn de la font del cos.measureGlyphWidthdona una mesura real sobre la font triada, i manté la mesura coherent quan es canvia de font. - Es reescriu la pàgina. El visor HTML tracta cada "pàgina" del VDT com una columna en pantalla. L'exemple sobreescriu
page.widthamb l'amplada mesurada de columna, posa els marges a zero (el padding viu fora de la pàgina, al.pt-docque l'embolcalla) i fa servirHTML_DPI = 144perquè8ptde cos resolgui a16px. - Atent a la càrrega de fonts.
document.fonts.loadingdonees dispara quan arriba una web font acabada de sol·licitar. Si no es recompon llavors, el primer render fa servir mètriques de la font de reserva i es produeix un salt quan arriba la real. - Es reutilitza la memòria cau de mesures. Crear la memòria cau una sola vegada per component fa que els redimensionaments i els canvis d'escala de font reutilitzin mesures del render anterior en lloc de tornar a mesurar cada paràgraf.
#Pas següent
L'exemple de més amunt és deliberadament pla. Les integracions en producció hi solen afegir:
- Aïllament amb Shadow DOM — renderitza a
host.attachShadow({ mode: 'open' })perquè res del document extern filtri CSS al visor. - Apedaçament incremental —
renderToHtmlIndexedretornapages[i].blocks, cadascun amb unidestable i l'HTML extern del bloc. Quan només uns quants blocs difereixen entre dos renders pots substituir aquests embolcalls al lloc en lloc de reconstruirinnerHTML. - Superposicions — apila un SVG absolut sobre cada
.pt-pageper a cursors, seleccions o la retícula de base. - Enllaços — les paraules d'un enllaç Markdown s'embolcallen en
<a href="…" rel="noopener noreferrer">, que pren el color del text i no se subratlla; consulta Format del document › Enllaços. En un visor que funcioni com a editor, intercepta els clics alsa[href]que no comencin per#i obre'ls en una pestanya nova (les àncores de:refenllacen dins del document). - Imatges a una sola tinta — amb
diagramStyle.singleInkactiu, els<img>SVG porten un filtre CSS, tret que passissingleInk: falseperquè les URL ja estan recolorides; consulta Tinta única en canvas i en HTML.
El component HtmlPreview del sandbox (packages/postext-sandbox/src/viewport/HtmlPreview/index.tsx) implementa tot això sobre la mateixa API que es mostra aquí i et pot servir de referència. A més, encamina cada build a través d'un worker de layout compartit (consulta Executar la composició en un Web Worker) perquè les edicions en directe i els redimensionaments no bloquegin mai el fil principal — substitueix la crida directa buildDocument(...) del fragment anterior per layoutWorker.build(...) quan vulguis treure la composició del fil principal.
#En què es diferencia la sortida HTML del canvas i del PDF
renderToHtml col·loca cada línia, figura i element de disseny exactament on ho fan el canvas i el PDF, però pinta menys coses al voltant:
| Element | Canvas (renderPage) | HTML (renderToHtml) | PDF (renderToPdf) |
|---|---|---|---|
| Fons de pàgina | Blanc, amb page.backgroundColor sobre la caixa de tall i la sang. | Transparent, tret que passis background o fixis page.backgroundColor (que llavors omple tota la caixa de la pàgina, inclosa la zona de les marques de tall). | Blanc, amb page.backgroundColor sobre la caixa de tall i la sang. |
Retícula de base (page.baselineGrid) | Es dibuixa | No es dibuixa | Es dibuixa |
Filet entre columnes (layout.columnRule) | Es dibuixa | No es dibuixa | Es dibuixa |
Marques de tall (page.cutLines) | Es dibuixen | No es dibuixen; la caixa de la pàgina continua incloent el marge exterior de les marques. | Es dibuixen |
| Negatiu de pàgina | Opció pageNegative | No disponible | Opció pageNegative |
| Text | Píxels | Text seleccionable en elements amb posició absoluta, compost en les famílies CSS: la pàgina ha de carregar les mateixes cares. | Fonts incrustades des del teu fontProvider; seleccionable, cercable i etiquetat. |
Text vertical (layout.writingMode: 'vertical-rl') | Caràcters pintats casella a casella i adreçats; formes verticals mitjançant una font bessona (loadVerticalAlternates). | El flux en una caixa girada un quart de volta; cada línia adreçada i composta amb writing-mode: vertical-rl, de manera que el navegador agafa les formes verticals i posa els caràcters drets; els números curts, en text-combine-upright: all; un guió llarg, uns punts suspensius, un punt volat o una titlla d'ona, en una caixa de la llargada de la seva cel·la (el navegador els faria avançar la seva amplada horitzontal), i el guió llarg estirat fins a omplir-la segons flow.dashAdvances. | Caràcters drets mitjançant una bessona Identity-V de cada font; vegeu Text vertical en el PDF. |
| Imatges | registerResourceImage | L'opció resourceImageUrl(fileId); sense ella, una caixa grisa provisional. | L'opció resourceBytes(fileId). |
| Fórmules | Traços vectorials | <svg> en línia | Traços vectorials |
| Enllaços | Cap | Les cites :ref enllacen amb el seu recurs; les entrades de l'índex, no. | Les cites :ref i les entrades de l'índex, a més dels marcadors. |
La pàgina transparent és important en un lloc amb tema fosc: una previsualització sense background mostra text negre sobre el fons fosc del lloc. Passa renderToHtml(doc, { background: '#ffffff' }), o dona al document un page.backgroundColor.
Els estils de text de la pàgina amfitriona queden fora. Cada línia es compon amb les amplades que va mesurar el motor, de manera que un letter-spacing, un word-spacing, un text-transform o un font-variant que la sortida heretés de la pàgina que la conté eixamplaria els trams de glifs i les línies es muntarien les unes sobre les altres. Per això l'arrel .pt-doc restableix les propietats de text heretables —espaiat entre lletres i entre paraules, caixa, sagnat, espais en blanc, estil, variant, pes, amplada, trets i kerning de la font, interlineat, alineació, ombra i èmfasi del text, guionets, direcció, mode d'escriptura, traç i farciment del text i l'engrandiment del text en mòbils— abans de les seves pròpies declaracions de maquetació, de manera que la sortida es veu igual dins d'una shadow root o sota un element amb estils. La llista s'exporta com a HTML_TEXT_RESET, una cadena de declaracions CSS: un amfitrió que munta l'innerHtml de les pàgines (de renderToHtmlIndexed) en contenidors propis l'aplica a la seva arrel. Fins a postext 1.4 l'arrel no restablia res; el remei era un embolcall amb all: initial.
#Generació de PDF
La sortida PDF viu en un paquet separat, postext-pdf, perquè les integracions purament web no paguin el cost de pdf-lib ni de @pdf-lib/fontkit. El backend de PDF no torna a mesurar el text: consumeix exactament el mateix VDTDocument que passaries a renderToCanvas o renderToHtml i tradueix les seves coordenades en píxels a punts PDF. Per tant, les tres sortides tenen garantit que coincideixen en salts de línia, alçades de columna i col·locació de recursos.
Al navegador, construeix el VDT a través del Web Worker.
renderToPdfen si és ràpid un cop existeix el VDT — la part costosa és el pipeline de composició que el va produir. Executar aquest pipeline al worker manté la UI fluida i permet que una exportació PDF reutilitzi la mateixa memòria cau de mesura que ja va escalfar la previsualització en directe. Consulta Exportar PDF des del worker per al flux recomanat. Els exemples al fil principal que segueixen són la referència de què signifiquen els arguments — per a codi d'UI, construeix primer el VDT al worker i cridarenderToPdfdirectament.
#Instal·lació
npm install postext postext-pdfpostext és una peer dependency de postext-pdf. Cada versió de postext-pdf necessita el postext amb què es va publicar, o un de posterior de la mateixa versió major (el seu rang és ^ aquesta versió, ^1.5.0 per a la 1.5.0), perquè importa funcions que postext va afegir en aquesta versió. Actualitza'ls tots dos alhora i, en un CDN, fixa'ls a la mateixa versió.
#API pública
El paquet exposa un únic punt d'entrada i un grapat de tipus:
renderToPdf(doc, options): Promise<Uint8Array>— pren unVDTDocument(o els capítols d'un llibre, com una llista d'aquests) i retorna els bytes en brut del PDF.PdfFontProvider— la signatura de callback(family, weight, style, request?) => Promise<Uint8Array | Uint8Array[]>querenderToPdffa servir per demanar els bytes d'una font quan necessita incrustar una combinació family/weight/style nova.request.codePointsconté els caràcters que les pàgines componen en aquesta variant; la resposta és un fitxer, o diversos que junts formen la variant (consulta Fonts xineses, japoneses i coreanes).RenderToPdfOptions—{ fontProvider, resourceBytes?, outlines?, accessible?, colorSpace?, pageNegative?, characterGrid?, onProgress?, onWarning?, rasterizeSvg?, harfbuzzWasm? }.outlines,accessibleicolorSpaceprenen el valor delpdfGenerationdel document quan no es passen (vegeu Generació de PDF (configuració)).resourceByteses descriu a Bytes de recursos i màsters d'impressió;onWarning, a Quines variants es demanen al proveïdor i a Avisos del document. AmbcharacterGrid: trueel PDF imprimeix la retícula quecjk.grid.showdibuixa en pantalla i que altrament deixa fora (vegeu Retícula de caràcters).harfbuzzWasmindica d'on carregar elharfbuzz.wasmde HarfBuzz (una URL, relativa a la pàgina, o els bytes del fitxer) per a un document amb text de dreta a esquerra o de lletres enllaçades; si no es passa, la còpia al costat del mòdul de postext-pdf i, després, la mateixa versió de harfbuzzjs a jsDelivr i a esm.sh.PdfWarning— un problema no fatal que es notifica peronWarning; es distingeix perkind:'fontFallback'(PdfFontFallbackWarning), una variant composta amb un altre tall de la seva família;'missingGlyph'(PdfMissingGlyphWarning), caràcters per als quals cap fitxer d'una variant té glif;'variableFontDefaultInstance'(PdfVariableFontWarning), una font variable demanada amb un pes diferent del de la seva instància per defecte;'cffEmbeddedWhole'(PdfCffEmbeddedWholeWarning), una font CFF de més de 2 MB incrustada sencera;'complexShapingUnavailable'(PdfComplexShapingWarning), text de dreta a esquerra o de lletres enllaçades dibuixat sense HarfBuzz, que no s'ha pogut carregar (reasondiu on s'ha buscat), de manera que les marques de l'àrab queden mal col·locades; o'missingImage', una imatge sense bytes dibuixada com a marcador de posició (aquesta només es notifica a unonWarningpropi; sense ell, els avisos de fonts van aconsole.warn).decompressWoff2(bytes): Uint8Array— helper que converteix un fitxer WOFF2 en bytes TTF, que és el format quepdf-libpot incrustar directament.createPdfWorker(options?), depostext-pdf/worker— el mateix render en un Web Worker; consulta Renderitzar el PDF en un worker.
#Exemple mínim
import { buildDocument } from 'postext';
import { renderToPdf } from 'postext-pdf';
const vdt = buildDocument(
{ markdown: '# Capítol u\n\nLa història comença aquí…' },
{
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt sobreescriu el valor per defecte de 8 pt
},
);
const pdfBytes = await renderToPdf(vdt, {
fontProvider: async (family, weight, style) => {
// Retorna els bytes TTF per a aquesta family/weight/style.
// Consulta la secció "Proveïdor de fonts" més avall per a una implementació real.
const res = await fetch(`/fonts/${family}-${weight}${style === 'italic' ? 'i' : ''}.ttf`);
return new Uint8Array(await res.arrayBuffer());
},
});
// `pdfBytes` és un Uint8Array — desa'l, baixa'l o envia'l per streaming.
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);#Per què un proveïdor de fonts?
pdf-lib incrusta fitxers de font reals dins del PDF — les fonts instal·lades al navegador no estan disponibles en el moment del render, i una font que només vas carregar per a la mesura en pantalla no basta, per si sola, per produir un PDF autocontingut. renderToPdf recorre les pàgines buscant cada variant que pinten (una per cada combinació family|weight|style; consulta Quines variants es demanen al proveïdor) i invoca el teu proveïdor una sola vegada per combinació única. El proveïdor retorna un Uint8Array amb bytes TTF o OTF, o una llista d'aquests per a una variant servida en diversos fitxers (consulta Fonts xineses, japoneses i coreanes); pdf-lib redueix els contorns TrueType als glifs usats i incrusta sencers els fitxers CFF (.otf). Les variants a les quals el proveïdor respon amb el mateix fitxer, com una rodona que substitueix la negreta que falta en una família, comparteixen una sola font incrustada. Una variant amb què cap pàgina arriba a dibuixar, com la del text d'una figura SVG que acaba dibuixada com a imatge, no s'escriu al fitxer.
Fes servir fonts estàtiques per pes, no una única font variable. Google Fonts sovint serveix un únic WOFF2 variable per família que cobreix tot l'eix de pesos. pdf-lib només pot incrustar la instància per defecte d'un fitxer variable, de manera que un paràgraf en negreta es renderitzaria amb pes regular. Fontsource publica fitxers WOFF2 estàtics per pes que resolen això netament — és el patró que fa servir el sandbox. Un fitxer variable demanat amb un pes diferent del de la seva instància per defecte es notifica amb un avís variableFontDefaultInstance.
Cada paraula del text cau on la va posar la maquetació. En els paràgrafs, els elements de llista, les cites, els requadres i la resta del text corregut, cada paraula comença a la posició que va mesurar el VDT, de manera que una diferència entre les amplades del navegador i les de la font incrustada mai no s'acumula al llarg d'una línia. Una línia sense format en línia es pinta com un sol objecte de text que mou el punt d'escriptura entre paraula i paraula; les línies justificades, les centrades i les que porten format es pinten paraula a paraula. Un caràcter per al qual la font no té glif, que el navegador va mesurar amb una altra font i que el PDF pinta com la caixa de glif absent de la font, no mou cap de les paraules que el segueixen, i es notifica una vegada per variant amb un avís missingGlyph. Un espai que la font no té, com l'espai fi de no separació o l'espai de xifra, pren l'amplada que li va donar el navegador, i els caràcters invisibles, com la unió de paraules i l'espai d'amplada zero, no es dibuixen. Un guionet de no separació (U+2011) que la font no té es dibuixa amb el guionet de la font (U+2010), o amb el seu guionet-menys si tampoc no té guionet, tal com el mostra el navegador; Open Sans i Outfit, entre d'altres, no tenen cap dels dos. Cap d'aquests casos no compta com a glif absent. Hi ha dues excepcions. Una línia amb lletres de dreta a esquerra es continua pintant com un sol tram (consulta Llengües i escriptures). El text que col·loca un disseny (capçaleres i peus correguts, obertures, títols de requadre i altres elements de disseny) es compon amb les amplades pròpies de la font incrustada, de manera que allà un glif que falta a la font continua movent la resta de la seva línia.
#Quines variants es demanen al proveïdor
renderToPdf recorre les pàgines en el mateix ordre en què les pinta i només demana al proveïdor les variants que dibuixen:
- la variant regular d'un bloc que compon alguna línia, i la negreta, cursiva o negreta cursiva de cada tram que de debò porta aquest estil;
- els trams dels xips, les marques de llista i el text dels dissenys (titolets, folis, bandes d'obertura i de part);
- el text del peu, de la nota i de les cel·les de taula de cada recurs;
- les variants que anomena el
<text>d'una figura SVG, en incrustar-la. Si el proveïdor no pot servir cap variant d'una família, es passa a la família següent de la llistafont-familyde l'SVG.
Així, mai no es demana la cursiva d'una família d'encapçalaments que ningú no inclina, i una figura sense nota no necessita les variants de la nota.
Quan el proveïdor rebutja una variant, el render continua endavant. S'incrusta al seu lloc una altra variant de la mateixa família i s'emet un PdfWarning. La substituta és la primera variant que carrega, provant els nou pesos estàndard (de 100 a 900) en l'ordre que fa servir la selecció de fonts de CSS, que és també la variant que el navegador mostra a la previsualització:
- primer el mateix estil. Per a un pes entre 400 i 500, van primer els pesos fins a 500, després els més lleugers, del més proper cap avall, i després els més gruixuts, de 600 cap amunt. Per a un pes per sota de 400, van primer els més lleugers, del més proper cap avall, i després els més gruixuts. Per a un pes per sobre de 500, van primer els més gruixuts i després els més lleugers;
- després l'altre estil, cursiva per a la rodona i rodona per a la cursiva, en el pes demanat i en els altres pesos amb el mateix ordre.
Per això una família sense cursives compon els seus trams en cursiva en rodona, una família que només publica 400 i 700 pren un 600 com a 700, i una família que publica un únic tall ho compon tot amb ell. Al proveïdor se li demana una variant cada vegada, en aquest ordre, i mai dues vegades la mateixa, de manera que mai no s'incrusta una variant que res no fa servir. A una família que el proveïdor no pot servir en absolut se li demanen aquestes 18 variants abans que el render falli.
const bytes = await renderToPdf(doc, {
fontProvider,
onWarning: (w) => {
// { kind: 'fontFallback', family: 'Oswald', weight: 700, style: 'italic',
// fallback: { weight: 700, style: 'normal' }, reason: '…', message: '…' }
console.info(w.message);
},
});Sense onWarning, el missatge va a console.warn. El text conserva les seves posicions, que vénen del VDT i es van mesurar amb les variants que tenia el navegador, de manera que una substituta amb altres amplades es pot veure atapeïda o fluixa. Per corregir-ho, serveix la variant real. Un render només falla (postext-pdf: failed to load font(s): …) quan el proveïdor no pot servir cap variant d'una família en un pes estàndard, ni rodona ni cursiva.
#Proveïdor de fonts al navegador (Fontsource + WOFF2)
El sandbox distribueix createPdfFontProvider() (packages/postext-sandbox/src/viewport/pdfFontProvider.ts), que pots copiar a qualsevol app de navegador. L'essencial:
import type { PdfFontProvider } from 'postext-pdf';
import { decompressWoff2 } from 'postext-pdf';
const bytesCache = new Map<string, Promise<Uint8Array>>();
function fontsourceId(family: string): string {
return family.toLowerCase().replace(/\s+/g, '-');
}
function fontsourceWoff2Url(
family: string,
weight: number,
style: 'normal' | 'italic',
): string {
const id = fontsourceId(family);
return `https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`;
}
export function createPdfFontProvider(): PdfFontProvider {
return async (family, weight, style) => {
const key = `${family}|${weight}|${style}`;
const cached = bytesCache.get(key);
if (cached) return cached;
const promise = (async (): Promise<Uint8Array> => {
const url = fontsourceWoff2Url(family, weight, style);
const res = await fetch(url, { mode: 'cors' });
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${url}`);
// pdf-lib necessita bytes TTF, així que descomprimim l'embolcall WOFF2 al client.
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
})();
bytesCache.set(key, promise);
return promise;
};
}Una versió de producció hauria de, a més:
- Consultar els pesos disponibles (via
https://api.fontsource.org/v1/fonts/{id}) i ajustar el pes demanat al més proper que la família distribueix realment, perquè una petició deweight: 600sobre una família que només té{400, 700}continuï funcionant. - Passar d'italic a normal quan una família no tingui tallada la itàlica per al pes sol·licitat, en lloc de fer fallar tot el render.
- Reutilitzar la memòria cau entre renders (mantén
bytesCachea nivell de mòdul, no per crida) perquè regenerar el PDF després d'un canvi de configuració sigui pràcticament gratuït.
#Fonts xineses, japoneses i coreanes
Una font CJK no ve en un sol fitxer petit. Fontsource distribueix Noto Serif SC en un centenar de fitxers per pes, cadascun amb una part dels caràcters i declarat al full d'estils de la família amb el seu unicode-range (@fontsource/noto-serif-sc/400.css); el navegador baixa els fitxers que toca el text de la pàgina. El fitxer latin que demana el proveïdor de dalt no té ni un sol caràcter han, i els subconjunts amb nom estan incomplets: al chinese-simplified de Noto Serif SC li falta 釵, i el chinese-traditional de Noto Serif TC no té cap dels signes d'amplada completa (),!?:;.
Per això un proveïdor pot respondre a una variant amb diversos fitxers. renderToPdf li passa els caràcters que les pàgines componen en aquesta variant (request.codePoints), reunits de tots els capítols abans de dibuixar res, i el proveïdor retorna els fitxers que els contenen, en l'ordre en què el navegador els consulta. Cada fitxer s'incrusta com un subconjunt propi i cada caràcter es dibuixa amb el primer fitxer que té glif per a ell: un capítol que toca 60 fragments incrusta 60 subconjunts petits. Si es torna a demanar la variant, per exemple per al text d'una figura SVG, només es demanen els caràcters que falten als seus fitxers. Un proveïdor que retorna un sol Uint8Array funciona com abans. El proveïdor del sandbox llegeix el full d'estils de Fontsource del pes i l'estil i baixa els fitxers els intervals dels quals contenen el text; el seu nucli:
import type { PdfFontProvider } from 'postext-pdf';
import { decompressWoff2 } from 'postext-pdf';
type Slice = { url: string; ranges: Array<[number, number]> };
async function fontsourceSlices(family: string, weight: number, style: 'normal' | 'italic'): Promise<Slice[]> {
const id = family.toLowerCase().replace(/\s+/g, '-');
const cssUrl = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/${weight}${style === 'italic' ? '-italic' : ''}.css`;
const css = await (await fetch(cssUrl)).text();
return [...css.matchAll(/@font-face\s*{([^}]*)}/g)].map(([, rule]) => ({
url: new URL(/url\(\.?\/?([^)]+\.woff2)\)/.exec(rule)![1], cssUrl).href,
ranges: /unicode-range:\s*([^;]+);/.exec(rule)![1].split(',').map((part) => {
const [lo, hi = lo] = part.trim().slice(2).split('-');
return [parseInt(lo, 16), parseInt(hi, 16)] as [number, number];
}),
}));
}
export const sliceFontProvider: PdfFontProvider = async (family, weight, style, request) => {
// Where ranges overlap, the browser tries the last rule first.
const slices = (await fontsourceSlices(family, weight, style)).reverse();
const picked = new Set<Slice>();
for (const cp of request?.codePoints ?? []) {
const slice = slices.find((s) => s.ranges.some(([lo, hi]) => cp >= lo && cp <= hi));
if (slice) picked.add(slice);
}
if (picked.size === 0) picked.add(slices[0]!);
return Promise.all(slices.filter((s) => picked.has(s)).map(async (s) =>
decompressWoff2(new Uint8Array(await (await fetch(s.url)).arrayBuffer()))));
};Una família llatina passa pel mateix codi: un text en anglès rep només el seu fitxer latin, un en txec rep latin i latin-ext.
Els caràcters per als quals cap fitxer de la variant té glif es dibuixen amb el glif .notdef de la font (una caixa buida en la majoria), i renderToPdf els notifica una vegada per variant quan acaba de dibuixar les pàgines:
// { kind: 'missingGlyph', family: 'Noto Serif TC', weight: 400, style: 'normal',
// characters: [',', '!', '?'], message: '…' }El Sandbox mostra aquests avisos, i els dos següents, al seu tauler Revisió després de cada PDF que genera. Si canvien el llibre, la seva configuració o els seus recursos, es marquen com d'un PDF anterior fins que el següent els substitueix; obrir un altre llibre els retira.
- La negreta necessita un fitxer estàtic per pes. Fontsource serveix cada pes de Noto Serif SC i TC en fitxers estàtics propis, de manera que la negreta funciona al Sandbox. Els fitxers de Google Fonts (
NotoSerifSC[wght].ttf, 25 MB) són fonts variables: pdf-lib incrusta la seva instància per defecte, de manera que una variant 700 s'imprimeix amb pes 400, irenderToPdfho notifica com avariableFontDefaultInstance. Per a un paquet, treu una instància estàtica per pes amb fontTools (fonttools varLib.instancer NotoSerifSC[wght].ttf wght=700) i redueix-la als caràcters del llibre ambpyftsubset. - Fes servir les versions TrueType. Source Han Serif i els fitxers
.otfde Noto Serif CJK tenen contorns CFF, que postext-pdf incrusta sencers, de 8 a 25 MB per pes; una font CFF de més de 2 MB es notifica com acffEmbeddedWhole. Les versions TrueType (Google Fonts, Fontsource) es redueixen als glifs usats.
Un caràcter que falta en una família no es pren d'una altra: Noto Serif TC no manlleva res de Noto Serif SC. Resol la cobertura en preparar els fitxers de font; l'exemple de 红楼梦 copia als seus subconjunts TC els glifs que els falten des de la font SC.
#Proveïdor de fonts al servidor (Node, fitxers locals)
A Node pots saltar-te completament el pas WOFF2 i llegir fitxers TTF/OTF des del disc:
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import type { PdfFontProvider } from 'postext-pdf';
const FONT_DIR = '/ruta/a/fuentes';
function filename(family: string, weight: number, style: 'normal' | 'italic'): string {
const slug = family.replace(/\s+/g, '');
const styleSuffix = style === 'italic' ? 'Italic' : '';
const weightName =
weight >= 700 ? 'Bold'
: weight >= 600 ? 'SemiBold'
: weight >= 500 ? 'Medium'
: weight >= 300 ? 'Light'
: 'Regular';
return `${slug}-${weightName}${styleSuffix}.ttf`;
}
export const localFontProvider: PdfFontProvider = async (family, weight, style) => {
const buf = await readFile(join(FONT_DIR, filename(family, weight, style)));
return new Uint8Array(buf);
};#Bytes de recursos i màsters d'impressió
resourceBytes(fileId) retorna els bytes en brut d'una imatge, i el backend els identifica pel seu contingut:
- PNG, JPEG, GIF i WebP s'incrusten com a imatges;
- el marcatge SVG es dibuixa com a traçats vectorials, o es rasteritza a 600 ppp al navegador quan fa servir funcions que queden fora del subconjunt vectorial;
- d'un PDF s'incrusta tal qual la primera pàgina, com un XObject de formulari.
Cada imatge es desa una sola vegada al fitxer, encara que es dibuixi moltes vegades. Un SVG dibuixat amb traçats vectorials es converteix en un XObject de formulari que pinta cada pàgina, de manera que un marc o un logotip en el disseny de pàgina d'un document de trenta pàgines s'escriu una vegada i no trenta; cada pàgina més afegeix uns centenars de bytes. Fins a postext-pdf 1.4 cada pàgina portava la seva pròpia còpia dels traçats.
Una figura SVG pot anomenar un màster d'impressió a svg.pdfFileId: un PDF d'una sola pàgina amb la mateixa figura, normalment l'original del qual es va exportar l'SVG. renderToPdf demana primer a resourceBytes l'identificador del màster. Incrusta aquesta pàgina en lloc de l'SVG, amb les seves fonts, degradats i espais de color intactes, allà on es dibuixi l'SVG: com a figura, com a imatge d'una cel·la de taula (TableCell.image), com a imatge de disseny o com a icona d'una caixa (també el seu marker). El VDT porta l'identificador del màster en cadascun d'aquests usos (svg.pdfFileId al recurs de la figura, pdfFileId a la imatge d'una cel·la i en un bloc d'imatge de disseny), de manera que en un llibre tots els capítols reben el màster. Els backends canvas i HTML continuen dibuixant l'SVG. En canvi, es fan servir els bytes del mateix SVG en tres casos: falta el màster, no és un PDF, o la tinta única està activa (diagramStyle.singleInk només recoloreja marcatge SVG).
const resources: Resource[] = [{
id: 'mapa', typeId: 'figure', kind: 'svg', createdAt: 0, updatedAt: 0,
svg: { fileId: 'mapa.svg', width: 800, height: 600, pdfFileId: 'mapa.pdf' },
}];
const archivos = new Map([['mapa.svg', bytesSvg], ['mapa.pdf', bytesMasterPdf]]);
const pdf = await renderToPdf(buildDocument({ markdown, resources }, config), {
fontProvider,
resourceBytes: (fileId) => archivos.get(fileId),
});Un amfitrió també pot retornar els bytes del màster per a l'identificador del mateix SVG, com fa bundleResourceBytes; les dues formes funcionen.
#Text vertical en el PDF
Una pàgina vertical (layout.writingMode: 'vertical-rl') es dibuixa a través d'un marc girat un quart de volta, tal com la pinta el canvas, i el seu text es compon columna avall:
- Els caràcters drets es mostren amb una segona font Type0 del mateix fitxer incrustat: la mateixa CIDFont, les mateixes amplades i el mateix mapa ToUnicode, amb
Encoding /Identity-V(mode vertical). Un tram de caràcters és un sol objecte de text els glifs del qual baixen un quadratí per la columna per si mateixos (DW2 [880 −1000]), de manera que els lectors seleccionen i extreuen una columna com una línia. Els glifs es conformen ambvertifwidd'OpenType, que donen la seva forma vertical als parèntesis, les cometes, els signes de pausa de la Xina continental, els punts suspensius i els guions llargs; un caràcter que queda dret tal qual conserva el seu glif horitzontal. No s'incrusta res de la font dues vegades. - Les paraules llatines i els números llargs van ajaguts amb la font horitzontal; un número en una casella queda dret, estrenyit fins al quadratí si és més ample; un signe sense forma vertical a la font es gira o es desplaça, com al canvas.
- L'espaiat entre caràcters s'escriu com a números de
TJ, que en mode vertical baixen el punt d'escriptura per la columna. - Cada línia vertical porta un
/ActualTextamb el seu text, de manera que copiar i extreure el text la llegeixen tal com es va escriure.pdftotexti pdf.js llegeixen les columnes de dalt a baix i de dreta a esquerra; pdf.js comença una línia nova en un número compost en una casella. - Els enllaços, els marcadors i les destinacions es porten al plec: un enllaç sobre una línia vertical és un rectangle alt i estret, i un marcador obre la pàgina a dalt de la columna del seu encapçalament.
- Un PDF etiquetat declara el sentit d'escriptura al seu element
Document(l'atribut de maquetacióWritingMode /TbRl, que hereten tots els elements); la validació PDF/UA-1 (veraPDF) passa en un capítol vertical. - Visors: Acrobat, Previsualització, Chrome (PDFium), pdf.js i Poppler pinten les fonts verticals. Un llibre enquadernat per la dreta (
page.binding) demana a més als visors que mostrin els plecs de dreta a esquerra (/Direction /R2L,/PageLayout /TwoPageRight); Acrobat i Foxit ho segueixen, Chrome no.
Un capítol de 43 pàgines compost en Noto Serif TC (els caràcters del llibre, TrueType) ocupa uns 820 KB, gairebé tot dels dos subconjunts de la font.
#Enllaços en el PDF
Les paraules d'un enllaç Markdown (consulta Format del document › Enllaços) es converteixen en anotacions d'enllaç URI, una per cada tram de paraules enllaçades d'una línia. Cada anotació cobreix la caixa de la línia i no porta vora. En un render accessible, cada tram és un element Link el /Contents del qual és el seu text. Només s'enllacen destinacions absolutes http:, https:, mailto:, tel: i ftp:, perquè una URL relativa no té base dins d'un PDF. Els caràcters que queden fora de l'ASCII imprimible es codifiquen amb percentatges. Les cites :ref i les files de l'índex conserven els seus enllaços interns al document.
#Exemple complet al navegador: compondre, renderitzar, baixar
Ajuntant-ho tot — construir el VDT, renderitzar a PDF i disparar la baixada des del navegador:
import { buildDocument, createMeasurementCache } from 'postext';
import { renderToPdf } from 'postext-pdf';
import { createPdfFontProvider } from './pdfFontProvider';
const fontProvider = createPdfFontProvider();
export async function downloadPdf(markdown: string, config: PostextConfig) {
const cache = createMeasurementCache();
const vdt = buildDocument({ markdown }, config, cache);
const bytes = await renderToPdf(vdt, { fontProvider });
const blob = new Blob([bytes.slice().buffer], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.pdf';
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}Important: crida ensureConfigFontsLoaded(config) (o equivalent) abans de buildDocument quan la teva configuració faci referència a web fonts. La maquetació es mesura amb les mètriques de font que el navegador tingui en aquell moment per a aquella família — si la font real encara no ha arribat, el VDT es mesura amb una de reserva i el PDF no coincidirà amb la sortida de canvas o HTML. El sandbox ho fa explícitament abans de cada render (vegeu packages/postext-sandbox/src/viewport/PdfViewport.tsx).
#Exemple en directe: un PDF al navegador
El flux complet de dalt, executant-se al navegador: el pen importa postext i postext-pdf des d'un CDN, carrega les fonts web, construeix el document, incrusta els talls de Fontsource a través del proveïdor de fonts i lliura els bytes a un enllaç que obre el fitxer en una pestanya nova i a un altre de baixada. El PDF resultant té els mateixos salts de línia que la sortida en canvas i HTML, fonts incrustades reals i marcadors d'esquema.
import { buildDocument } from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// The PDF embeds real font files. Fontsource publishes one static WOFF2 per
// weight and style; decompress it to the TTF bytes pdf-lib can embed.
const fontProvider = async (family, weight, style) => {
const id = family.toLowerCase().replace(/\s+/g, '-');
const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`;
const res = await fetch(url);
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${url}`);
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
};
// Layout is measured with the browser's fonts, so load them before building:
// otherwise the PDF would not match the canvas or HTML output.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
// Same VDT, now translated to PDF points: identical line breaks and placement.
const bytes = await renderToPdf(doc, { fontProvider });
// A PDF viewer cannot run inside this sandboxed result frame,
// so hand the file to a new tab and to a download link.
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
document.getElementById('open').href = url;
document.getElementById('download').href = url;
document.getElementById('links').hidden = false;
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · ${(bytes.length / 1024).toFixed(0)} KB PDF`;index.html
<p id="status">Rendering…</p>
<p id="links" hidden>
<a id="open" target="_blank" rel="noopener">Open lantern.pdf in a new tab</a> ·
<a id="download" download="lantern.pdf">Download it</a>
</p>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Renderitzar el PDF en un worker
postext-pdf/worker treu renderToPdf del fil principal. El worker escriu el text, les figures vectorials, l'arbre d'estructura i el mateix fitxer. Hi ha dues tasques que necessiten la pàgina, així que el worker les demana al fil principal: baixar les fonts i rasteritzar un SVG a través d'un <img>. En un llibre de centenars de pàgines el render triga segons que, altrament, congelarien la pàgina; per a unes poques pàgines, cridar directament renderToPdf és més senzill.
import { createPdfWorker } from 'postext-pdf/worker';
const pdfWorker = createPdfWorker();
const bytes = await pdfWorker.render(docs, {
fontProvider, // s'executa en aquest fil
resourceBytes: new Map([['mapa.svg', bytesSvg]]), // un Map; els seus buffers passen al worker
onProgress: ({ phase, pages, totalPages }) => mostrarProgreso(phase, pages, totalPages),
onWarning: (w) => console.info(w.message),
});
pdfWorker.dispose();render(docs, options)accepta un document o la llista de documents dels capítols d'un llibre. Accepta les opcions derenderToPdf, amb dues diferències.resourceBytesés unMap<string, Uint8Array>els buffers del qual es transfereixen, així que passa còpies dels bytes que vulguis conservar.rasterizeSvg, si s'indica, s'executa al fil principal; per defecte ho fan l'Imagei el canvas de la mateixa pàgina.- Un handle renderitza un document cada vegada.
dispose()acaba el worker i rebutja qualsevol render que continuï pendent. createPdfWorker({ worker })accepta unWorkerque creïs tu, per a les eines de build que controlen les URL dels workers. Aquest worker ha d'executarpostext-pdf/worker/entry.
Des d'un CDN. Per defecte, l'script del worker es carrega des de la URL del mateix paquet (new URL('./pdf.worker.js', import.meta.url)). Una pàgina d'un altre origen pot no poder arrencar-lo: importat des d'esm.sh, createPdfWorker() llança Failed to construct 'Worker': Script at 'https://esm.sh/postext-pdf@…/pdf.worker.js' cannot be accessed from origin …. Arrenca en el seu lloc un worker de mòdul del mateix origen que importi l'entrada (si fixes una versió, fixa la mateixa a les dues URL):
import { createPdfWorker } from 'https://esm.sh/postext-pdf/worker';
const entrada = URL.createObjectURL(new Blob(
["import 'https://esm.sh/postext-pdf/worker/entry';"],
{ type: 'text/javascript' },
));
const pdfWorker = createPdfWorker({ worker: new Worker(entrada, { type: 'module' }) });El worker de composició de postext/worker necessita el mateix embolcall al voltant de postext/worker/entry (consulta Executar la composició en un Web Worker).
#PDF a punt per a impremta
Per a fluxos de treball d'impressió en producció, ajusta aquestes opcions de configuració abans de renderitzar:
page.cutLines.enabled: true— afegeix àrea de sang i marques de tall al voltant del trim, i dona a cada pàgina una TrimBox i una BleedBox. Vegeu Marques de tall.colorSpace: 'cmyk'(opdfGeneration: { forceColorSpace: true, colorSpace: 'cmyk' }) — escriu en DeviceCMYK els colors que pinta postext (text, filets, fons, figures vectorials) i pinta les marques de tall en color de registre, perquè s'imprimeixin en totes les planxes. Els mapes de bits i els originals d'impressió en PDF s'insereixen tal com són, així que una foto RGB continua en RGB: converteix-la abans d'afegir-la.page.dpi: 300(o superior) — els punts PDF són fixos a 72/polzada, però l'aritmètica de maquetació de Postext funciona en píxels; un DPI més alt dona una subdivisió més fina per a elements mesurats enmmocm.colors.model: 'cmyk'— preserva la intenció que els colors es van definir en espai CMYK. El fallbackhexcontinua sent el que s'utilitza per dibuixar realment al PDF avui;modeles documenta aquí perquè viatja juntament amb el VDT per a eines posteriors.{ pageNegative: true }aRenderToPdfOptions— inverteix l'àrea de trim amb un blend mode de tipus Difference (les marques de tall queden sense invertir). Útil per a comprovacions de preflight sobre tipografia fosca sobre clar.
#Implementació de referència
El component PdfViewport del sandbox (packages/postext-sandbox/src/viewport/PdfViewport.tsx) connecta les peces anteriors en una previsualització en directe amb botons de regenerar, baixar i imprimir, i és un bon punt de partida per a qualsevol integració PDF al navegador. Construeix el VDT a través del worker de layout compartit (consulta Executar la composició en un Web Worker) perquè prémer Regenerar no congeli la UI mentre s'executa el pipeline — el fil principal només s'encarrega de renderToPdf (que ja és ràpid un cop existeix el VDT).
#Un llibre en 3D (postext-folio)
postext-folio presenta en pantalla un document compost com un llibre imprès obert sobre una taula: plecs segons la regla del recto i fulls que el lector passa amb els botons ‹ ›, les fletxes del teclat, lliscant el dit, amb un clic en una pàgina o agafant la pàgina per la vora i arrossegant-la. Cada full es corba en three.js segons el seu paper i projecta una ombra real sobre les pàgines que té a sota. El llenç WebGL dibuixa el llibre tant quiet com en passar la pàgina, així que una pàgina mai no canvia d'aspecte en posar-se. És el visor de les receptes del Receptari i de la pestanya Folio del sandbox.
npm install postext postext-folio threeimport { buildDocument } from 'postext';
import { createFolioFromDocument } from 'postext-folio';
const doc = buildDocument({ markdown }, config);
const book = createFolioFromDocument(document.getElementById('book')!, doc, {
onChange: ({ pages }) => console.log('pàgines a la vista', pages),
});
// Després d'una edició: el mateix visor, a la mateixa pàgina.
book.setDocument(buildDocument({ markdown: edited }, config));- Les pàgines es pinten a mesura que calen.
createFolioFromDocumentpinta cada pàgina ambrenderPageToCanvasexactament als píxels de dispositiu del seu espai (WebGL la mostra llavors tèxel a píxel, tan nítida com a la vista de llenç), i només els plecs propers a l'obert (window, tres a cada costat per defecte). Les pàgines que surten d'aquesta finestra s'alliberen, així que un llibre de mil pàgines ocupa la memòria d'unes poques. Un salt a una pàgina llunyana pinta primer aquell plec. Fins a deu pàgines, els fulls passen un a un; més enllà, el bloc de pàgines intermedi s'aixeca com una sola peça, tan gruixuda com aquestes pàgines (la suma dels seus calibres), i es posa a l'altre costat.setDocumentconserva el que s'ha pintat de cada pàgina que queda igual en la nova composició ({ repaint: true }les torna a pintar totes, per exemple quan acaba d'arribar una imatge). - El document defineix el llibre. La primera pàgina s'obre sola a la dreta quan és un recto (
pageIndexOffsetparell), un llibre enquadernat per la dreta (page.binding: 'right', o un document vertical) apareix en mirall i passa les pàgines cap a l'esquerra, les pàgines en blanc prenen el color de fons de la pàgina, i l'amplada de tall de la pàgina (pageWidthMm) escala el gruix del paper i les tapes. Un capítol compost amb unacontinuationsuma les altres pàgines del llibre (pageIndexOffsetabans,bookPageCountdesprés) al gruix dels dos blocs de pàgines sense dibuixar-les (extraPages). - El document defineix l'aspecte. El paper, l'enquadernació, la taula i la llum són la configuració
foliodel document (doc.config.folio). Una pàgina composta dins d'una tanda:::paperporta el seu propi paper (VDTPage.paper), i el seu full es dibuixa amb el color, la superfície, el gruix i la rigidesa d'aquell paper. Ambbinding.cover: 'pages', la primera pàgina és la tapa davantera i l'última, si és un verso, la del darrere (covers). - El contenidor defineix la mida. El llibre l'ocupa sencer, amb els botons i el comptador de pàgines als marges, així que dona-li una alçada; quan canvia de mida, les pàgines es tornen a pintar a la nova. Per sota de 560 px d'amplada mostra una pàgina cada vegada (
mode: 'auto';'single'i'double'forcen una o l'altra): el llom va per la vora interior de la pàgina i el full gira sobre ell; arrossegar cap al llom avança, lliscar en sentit contrari retrocedeix, i un toc passa la pàgina. - Què fa el punter.
interaction(i despréssetInteraction) fixa què fa el botó esquerre, un dit o un llapis sobre el llibre:'hand'(per defecte) agafa les pàgines i les passa,'orbit'gira la vista com en arrossegar amb el botó dret (per a trackpads i tauletes),'select'deixa el punter a l'amfitrió, per exemple per seleccionar text.pageAt(event)dona la pàgina sota un punter i el punt d'aquesta ({ page, x, y }, fraccions de la pàgina des de la cantonada superior esquerra), sobre el llibre tal com es veu, inclinat o girat;pointOnScreen(point)fa el camí invers, per dibuixar un cursor o una selecció sobre la pàgina.refreshPage(src)torna a mostrar el llenç d'una pàgina que l'amfitrió ha repintat al seu lloc. El Sandbox els fa servir tots per seleccionar text i seguir el cursor de l'editor sobre les pàgines en 3D. - El lector pot fer la volta al llibre. Arrossegar amb el botó dret gira la vista al voltant del llibre (fins a 70° des de la vertical), també mentre passen fulls;
resetView()la retorna suaument a latilti elyawde la configuració, igetView()dona la vista tal com es veu ara ({ tilt, yaw }, en graus) per desar-la en aquesta configuració. El Sandbox els ofereix en dos botons, Restableix la vista i Desa com a vista per defecte. - Abans, fonts i imatges. Com amb
renderPage, les fonts del document han d'estar carregades adocument.fontsi les seves imatges registrades ambregisterResourceImageabans de pintar les pàgines. - Accessible. El visor és un grup enfocable que respon a ←/→ (en mirall per a un llibre enquadernat per la dreta), Re Pàg/Av Pàg, Inici i Fi; els seus botons i el comptador de pàgines porten etiqueta (
labelsles tradueix), i cada llenç de pàgina porta text alternatiu (alt: (index) => …). - Sense WebGL2, o quan el lector demana menys moviment, els plecs simplement canvien. Un llibre en WebGL pesa massa per a un mòbil (una textura per cada cara de pàgina): el Sandbox ofereix la pestanya Folio només on hi ha WebGL2 i el costat curt de la pantalla mesura almenys 600 px.
canFlip()diu si els fulls passaran en 3D en aquell entorn: amb WebGL2 i sense preferència de menys moviment.
#Aspecte
L'opció appearance, i després setAppearance, substitueixen el que diu el document. El que no s'indiqui conserva el valor del document:
const book = createFolioFromDocument(container, doc, {
appearance: {
folio: {
tilt: 22,
paper: { type: 'bookWove', texture: 'laid' },
binding: { type: 'hardcover', coverColor: { hex: '#5a1f1f', model: 'hex' } },
surface: { type: 'walnut' },
lighting: { environment: 'lamp' },
},
// Taules fotografiades: una carpeta organitzada com /folio/textures/ de postext.dev
// (manifest.json i una carpeta per taula). Sense aquesta, mapes procedurals.
textureBaseUrl: '/folio/textures',
// La imatge de `folio.binding.spineImage`: la URL del recurs,
// o un llenç o una imatge ja dibuixats.
spineImage: spineUrl,
},
});
// Un tauler de configuració: el llibre es redibuixa al seu lloc, sense tornar a pintar res.
book.setAppearance({ folio: { ...folio, lighting: { environment: 'daylight' } } });
book.resetView();| Camp | Què fa |
|---|---|
folio | La configuració folio: inclinació, paper, enquadernació, superfície i llum. Si s'indica, substitueix la del document. |
pageWidthMm | L'amplada de tall d'una pàgina en mm, amb la qual s'escalen el gruix del paper i les tapes. Des del document: la pàgina tallada al seu dpi. Per defecte, 150 a createFolio. |
extraPages | : pàgines del llibre fora de les que es passen, que compten per al gruix dels blocs de pàgines i mai no es dibuixen. |
covers | : la primera pàgina que es passa és la coberta i l'última, la contracoberta (si cau en un verso). Passen com a tapes rígides i no es dibuixa cap tapa al voltant. Des del document: binding.cover: 'pages' en un llibre que comença a la primera pàgina i acaba a l'última. |
spineImage | La imatge impresa al llom, com a URL, llenç o imatge. createFolioFromDocument no cerca recursos: passa-li la imatge del recurs que anomena folio.binding.spineImage. No s'utilitza en el grapat a cavall. |
textureBaseUrl | On se serveixen les textures fotografiades de la taula. Mentre no es carreguen, o sense aquesta opció, la taula es dibuixa amb mapes procedurals. |
createFolio(container, { pages }) és el mateix visor amb qualsevol pàgina: URL d'imatges, elements <img> o <canvas>, i "" per a una pàgina en blanc; una pàgina pot ser { src, alt, paper }, amb paper un paper a la manera de :::paper per a aquell full. PageFlipper és el motor de three.js per separat, per a qui maqueta el seu propi DOM del plec; FlatPageFlipper és el pas de pàgina pla anterior al llibre en 3D, que es manté per a la taula de llum del Receptari. La llista completa d'opcions és al README del paquet.
#Exemple en directe: un llibre en 3D
El pen importa postext i postext-folio des d'un CDN, compon un document breu i l'obre com un llibre. Agafa la pàgina dreta per la vora i arrossega-la.
import { buildDocument } from 'https://esm.sh/postext';
import { createFolioFromDocument } from 'https://esm.sh/postext-folio';
const paragraph = `The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved. The light it gave was small, but it was enough to find the step.`;
// Thirty-six short sections: about ten pages to turn.
const markdown = ['# The Lantern']
.concat(Array.from({ length: 36 }, (_, i) => `## Evening ${i + 1}\n\n${paragraph} ${paragraph}\n\n${paragraph}`))
.join('\n\n');
const config = {
page: { sizePreset: '17x24', dpi: 150 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
const status = document.getElementById('status');
// The book: drag a page by its edge, click it, or use ← → and the buttons.
// Pages are painted at the size they are shown, around the open spread only.
createFolioFromDocument(document.getElementById('book'), doc, {
onChange: ({ pages }) => {
status.textContent = `${doc.pages.length} pages · open at ${pages.map((i) => i + 1).join('–')}`;
},
});
status.textContent = `${doc.pages.length} pages · drag a page by its edge to turn it`;index.html
<p id="status">Laying out…</p>
<div id="book"></div>style.css
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #eee;
background: radial-gradient(ellipse 70% 75% at 50% 42%, #272b34 0%, #1a1d23 58%, #121418 100%);
min-height: 100vh;
}
#status {
margin: 12px 16px 0;
font-size: 14px;
opacity: 0.8;
}
/* The viewer fits the book into its container: give it a height. */
#book {
height: calc(100vh - 48px);
--postext-folio-accent: #f0b35a;
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Exemple en directe: imatges de pàgina
createFolio amb pàgines dibuixades en llenços, una última pàgina en blanc i el color del paper.
import { createFolio } from 'https://esm.sh/postext-folio';
// Any pages will do: image URLs, <img> or <canvas> elements, and "" for a
// blank page. Here, eight pages drawn on canvases.
function drawPage(n) {
const canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 840;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fbf8f1';
ctx.fillRect(0, 0, 600, 840);
ctx.fillStyle = `hsl(${n * 45} 45% 45%)`;
ctx.fillRect(60, 80, 480, 320);
ctx.fillStyle = '#222';
ctx.font = 'bold 56px Georgia, serif';
ctx.fillText(`Plate ${n}`, 60, 480);
ctx.font = '22px Georgia, serif';
for (let line = 0; line < 8; line++) ctx.fillRect(60, 530 + line * 30, line === 7 ? 260 : 480, 3);
ctx.textAlign = 'center';
ctx.fillText(String(n), 300, 800);
return { src: canvas, alt: `Plate ${n}` };
}
const pages = Array.from({ length: 8 }, (_, i) => drawPage(i + 1));
// A blank page at the end, drawn as paper.
pages.push('');
const status = document.getElementById('status');
createFolio(document.getElementById('book'), {
pages,
firstPageRecto: true, // page 1 opens alone, on the right
binding: 'left', // 'right' lays a right-to-left book mirrored
paper: '#fbf8f1',
onChange: (state) => {
status.textContent = `Showing ${state.pages.map((i) => i + 1).join('–')} of ${pages.length}`;
},
});
status.textContent = 'Drag a page by its edge, click it, or use ← →';index.html
<p id="status">Drawing pages…</p>
<div id="book"></div>style.css
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #eee;
background: radial-gradient(ellipse 70% 75% at 50% 42%, #272b34 0%, #1a1d23 58%, #121418 100%);
min-height: 100vh;
}
#status {
margin: 12px 16px 0;
font-size: 14px;
opacity: 0.8;
}
/* The viewer fits the book into its container: give it a height. */
#book {
height: calc(100vh - 48px);
--postext-folio-accent: #f0b35a;
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Llibres EPUB (postext-epub)
postext-epub escriu un llibre maquetat com un fitxer EPUB 3.3, al navegador o a Node, sense servidor. Llegeix els mateixos documents per capítol que renderToPdf rep per a un llibre, de manera que els números de pàgina, les notes, les citacions, les referències creuades, el sumari i l'índex alfabètic arriben resolts, i retorna el fitxer com a bytes. És l'escriptor que fa servir la pestanya EPUB 3 del Sandbox.
npm install postext postext-epubpostext és una dependència peer, com a postext-pdf: actualitza tots dos alhora i, des d'un CDN, fixa'ls a la mateixa versió.
#Maquetació fixa i maquetació fluida
EPUB 3 defineix dues maquetacions, que el paquet declara amb la propietat rendition:layout; layout en tria una:
layout: 'fixed' | layout: 'reflowable' | |
|---|---|---|
| Nom a EPUB | pre-paginated (maquetació fixa, en anglès fixed layout o FXL) | reflowable (maquetació fluida), la de l'EPUB per defecte |
| Documents de contingut | Un document XHTML per pàgina impresa, amb la mida de la pàgina refilada en px CSS | Un document XHTML per capítol (una part n'obre un de propi) |
| Què conserva | La pàgina: columnes, flotants, capçaleres, obertures, talls de línia i posicions, amb les lletres incrustades. El text continua sent text: se selecciona, es cerca i es llegeix en veu alta | El text i la seva estructura: títols, paràgrafs reconstruïts a partir de les línies, llistes, requadres com a apartats, figures i taules després del text que les cita, notes, enllaços i marques de les pàgines impreses. Un full d'estil derivat de la configuració |
| Què perd | L'elecció de lletra, mida i marges de qui llegeix; en una pantalla petita la pàgina es veu reduïda | Les columnes, les capçaleres, el disseny de pàgina i els talls de línia exactes |
| Plec i sentit | page-spread-left / page-spread-right segons la paritat i l'enquadernació; un llibre enquadernat per la dreta es llegeix de dreta a esquerra | El sentit de lectura surt de l'enquadernació; el xinès vertical conserva vertical-rl i l'àrab porta dir="rtl" |
| Indicada per a | Pàgines dissenyades: llibres il·lustrats, llibres de text, catàlegs, revistes; pantalles grans | Text seguit: novel·les, assajos, informes; mòbils i lectors de tinta electrònica |
Les dues maquetacions porten la mateixa navegació: un sumari a partir dels títols i les pàgines de part, una llista de pàgines amb els números impresos, punts de referència (coberta, sumari imprès, començament del cos) i un NCX per a lectors antics.
#Escriure un llibre
import { openBundle, buildBundle } from 'postext';
import { renderToEpub } from 'postext-epub';
const bundle = await openBundle(fileBytes);
const docs = buildBundle(bundle); // un VDTDocument per capítol, en l'ordre del llibre
const bytes = await renderToEpub(docs, {
layout: 'reflowable',
metadata: { title: 'Llanterna', creators: ['Ada Lovelace'], language: 'ca' },
fonts: bundle.fonts.map((f) => ({ family: f.family, weight: f.weight, style: f.style, bytes: new Uint8Array(f.bytes), format: f.format })),
resourceBytes: (fileId) => {
const data = bundle.files.get(fileId);
return data ? { bytes: data, mediaType: '' } : undefined;
},
onWarning: (w) => console.warn(w),
});Un document sol és un llibre d'un capítol: renderToEpub([doc], options).
renderToEpub(docs, options): Promise<Uint8Array>escriu el fitxer.optionsés{ layout, metadata, fonts?, resourceBytes?, cover?, onProgress?, onWarning?, signal? }.metadata:titleilanguage(una etiqueta BCP 47) són obligatoris;subtitle,creators,identifier,date,publisher,rights,descriptionimodifiedsón opcionals. Un ISBN sense prefix passa aurn:isbn:…. Senseidentifier, el llibre rep unurn:uuid:derivat del títol, els autors i l'idioma, de manera que una versió nova del mateix llibre conserva el seu lloc a la biblioteca del lector. Passa tambémodifiedper obtenir un fitxer idèntic byte a byte.fonts: les variants que s'incrusten,{ family, weight, style, bytes, format, unicodeRange? }, ambformatun dewoff2,woff,ttf,otf. Cada variant passa a ser un fitxer i una regla@font-face; diversos fitxers amb el seuunicodeRangeformen una sola variant (els trams de Google Fonts). Una família, un pes o un estil que les pàgines fan servir sense variant incrustada s'avisa com amissingFont, i els lectors en posen una de seva. Incrusta només les lletres la llicència de les quals ho permeti.resourceBytes(fileId): les imatges que col·loquen les pàgines, de manera síncrona o asíncrona, com a{ bytes, mediaType }; unmediaTypebuit es llegeix dels bytes. Dona els mapes de bits tal com estan desats i els SVG com el seu codi font, no el màster PDF d'impremta (svg.pdfFileId). Cada imatge es desa una vegada. En un llibre a una tinta (diagramStyle.singleInk) els SVG es recoloren al fitxer. Una imatge sense bytes s'avisa com amissingImagei queda com un marc buit.cover:{ bytes, mediaType, alt? }, una imatge JPEG, PNG, WebP o SVG. Aleshores el llibre s'obre amb un document de coberta que la conté, i és lacover-imagedel paquet (la miniatura en una biblioteca). Sense coberta, la maquetació fixa anomena coberta la seva primera pàgina i el llibre fluid no té imatge de coberta.onProgress({ phase, done, total }):resources(lletres i imatges),documents(pàgines en la maquetació fixa, capítols en la fluida) i despréspackage.signalinterromp entre passos.readEpub(bytes)llegeix un fitxer per a un visor, senseDOMParser: maquetació, metadades, sentit de lectura, cada fitxer pel seu camí, el manifest, l'spine, el sumari, la llista de pàgines, el viewport de la maquetació fixa i la coberta. El lector del Sandbox s'hi basa.
Les dues maquetacions porten les metadades d'EPUB Accessibility 1.1 (modes d'accés, prestacions com el sumari i els números de pàgina impresos, riscos i un resum) i no declaren conformitat amb WCAG per defecte. La llista completa d'opcions i les limitacions són al README del paquet.
#Comprovar un fitxer amb EPUBCheck
W3C EPUBCheck és el validador de referència d'EPUB; les botigues de llibres electrònics hi comproven els fitxers que reben. Amb l'eina instal·lada (brew install epubcheck, o la versió Java), epubcheck llibre.epub enumera errors, avisos i notes d'ús; les notes d'ús que deixa la sortida de Postext (CSS-028, OBS-001, HTM_062) són informatives. Al repositori de Postext, pnpm --filter postext-epub epubcheck comprova els llibres de mostra de les proves, node packages/postext-epub/scripts/epubcheck.mjs llibre.postext --layout both maqueta un fitxer .postext o una carpeta de preset i comprova les dues maquetacions, i pnpm --filter postext-epub validate recorre tota la matriu de llibres (la guia, els presets de mostra i llibres en xinès, en àrab i del Receptari), que passen tots sense errors ni avisos.
#Paquets (fitxers .postext)
Un fitxer .postext és un llibre sencer en un sol fitxer: un zip amb un manifest preset.json, un fitxer markdown per capítol, les dades dels recursos (mapes de bits, SVG, màsters PDF d'impressió) i els fitxers de les fonts que anomena la configuració. El Sandbox l'exporta i l'importa i l'skill per a agents el lliura. El paquet postext també el sap crear i obrir, així que un llibre pot passar d'aquestes eines al teu propi programa, i a l'inrevés, sense perdre res.
mi-libro.postext
├── preset.json manifest: nom, llengua, capítols, configuració, recursos, fonts
├── chapters/01-anochecer.md
├── chapters/02-noche.md
├── resources/farol.svg
└── fonts/ebgaramond-400-normal.woff2
El manifest es descriu camp per camp a l'apèndix Format de paquet de preset del Sandbox. Un fitxer pot portar a més layouts.json, el recompte de pàgines del Sandbox, perquè el llibre s'hi obri ja paginat, o un per edició d'un llibre en diverses llengües (layouts.zh-Hant.json, que es llegeix primer). openBundle els ignora.
L'API s'exporta des de postext i des del subpath postext/bundle, que afegeix les utilitats de baix nivell. Importa des de postext quan a més hagis de renderitzar. Així els adaptadors de paquets i els renderitzadors comparteixen una sola instància del mòdul, cosa que importa en un CDN com esm.sh, on cada punt d'entrada és un build diferent.
#Obrir un paquet
openBundle rep els bytes del fitxer (un Uint8Array, un ArrayBuffer, o un Blob / File d'un <input type="file">) i retorna tot el que necessiten el motor i els seus backends:
import { openBundle } from 'postext';
const bundle = await openBundle(await file.arrayBuffer(), { locale: 'es' });
bundle.chapters; // [{ title, file, markdown }, …] en l'ordre del llibre
bundle.config; // PostextConfig, a punt per a buildDocument
bundle.resources; // Resource[]
bundle.files; // Map<ruta, Uint8Array>: tots els fitxers del paquet| Camp | Què conté |
|---|---|
manifest | El preset.json ja validat. |
id, name, description | Presos del manifest. |
locale, locales | La llengua en què s'ha llegit el contingut, i totes les llengües que porta un paquet bilingüe. options.locale en tria una: primer l'etiqueta exacta, després la llengua base, després la llengua pròpia del paquet. |
chapters | { title, file, markdown } per capítol. Un capítol sense títol al manifest pren el text del seu primer encapçalament #. |
config | La paleta de colors per defecte i els tipus de recurs en la llengua del paquet, després la config del manifest i després la configuració pròpia de la llengua. La llengua del paquet és el locale de dalt, així que un paquet en una sola llengua rep els seus propis rètols, demani el que demani options.locale. Un manifest que no anomena cap llengua pren la que fixa la seva config (locale i, si falta, la de la partició de mots) i, si tampoc no n'hi ha, options.locale. customFonts llista les famílies tipogràfiques del paquet. És la mateixa configuració amb què el Sandbox obre el paquet. |
resources | Els recursos, amb els peus de la llengua triada. Una mida que falti al manifest es llegeix del fitxer. |
fonts | Una entrada per variant: { family, weight, style, format, file, bytes }. |
files | Tots els fitxers del zip, per la seva ruta. |
thumbnail, canvasScope | La ruta de la portada, i com demana el paquet que es mostri: la view del manifest, amb la localized[…].view de la llengua servida per sobre. |
warnings | Problemes que no impedeixen obrir-lo: un fitxer de font no admès, un màster d'impressió que falta. |
El fileId de cada fitxer és la seva ruta dins del paquet. resource.svg.fileId, resource.bitmap.fileId i el fileId de cada variant de customFonts es cerquen directament a bundle.files. openBundle llança un error si els bytes no són un zip, si no hi ha un preset.json vàlid (a l'arrel o sota una única carpeta) o si falta un fitxer que el manifest anomena.
Paquets escrits per postext 1.4 o anterior
Tot manifest que escriuen createBundle i el Sandbox porta configVersion: 8: les regles de configuració per a les quals es va escriure la seva config. Un manifest sense aquest camp el va escriure postext 1.4 o anterior, que resolia tretze coses d'una altra manera:
- Els salts d'encapçalament (regles 3): fins a la 1.4, un objecte
headingssense salt d'H1 no en tenia cap (vegeu Configuració per nivell). - La mida de les fórmules (regles 4): fins a la 1.4, les fórmules sortien 1,131 vegades més grans del que diu
fontSizeScale(vegeu Mida de les fórmules). - L'espai sota un recurs en línia (regles 5): fins a la 1.4, el text que segueix una figura o taula amb
placement.position: 'here'continuava a la línia següent de la retícula, sense l'espai dels flotants a sota (vegeulayout.inlineResourceGapa Disposició). - L'espai al voltant d'un recurs en línia dins d'un requadre (regles 6): fins a la 1.4, aquest recurs quedava enganxat al text del requadre que l'envolta (vegeu
layout.inlineResourceGapInBoxesa Disposició). - Les marques en línia dels encapçalaments (regles 6): fins a la 1.4, un encapçalament imprimia les paraules de la seva
*cursiva*, la seva**negrita**i les seves altres marques amb el seu propi estil, sense marques (vegeuheadings.inlineMarksa Encapçalaments). - La mida d'una caplletra (regles 6): fins a la 1.4, el
dropCapd'un text de disseny sensefontSizeera tan alt com totes les caixes de línia que abasta, amb la part alta per damunt de la primera línia (vegeudropCapa Elements de text). - El lloc sota una línia de dos punts (regles 6): fins a la 1.4,
keepColonWithListdonava per bona per a la llista una línia de lloc sota la línia que acaba en dos punts, i un primer element de dues línies que les regles d'òrfenes i vídues mantenen sencer passava a la columna següent sense aquesta línia (vegeubodyText.colonListRoom). - Les línies que deixa el tall d'una caixa (regles 6): fins a la 1.4, una caixa que es partia dins d'un paràgraf o d'un element de llista podia deixar-ne una sola línia a un costat, sempre que cada costat de la caixa sumés les seves
splitMinLineslínies (vegeulayout.boxChildSplitMinLinesa Disposició). - Els salts de línia després d'un guió llarg (regles 7): fins a la 1.4, Knuth-Plass mai no acabava una línia després d'un guió llarg o un guió mitjà posat entre dues paraules sense espais (
Madrid–Barcelona,I.—Que trata), i el divisor línia a línia del text amb format només entre dues lletres (vegeubodyText.breakAfterDashesa Text de cos). - El text en bandera (regles 7): fins a la 1.4, el text corregut en bandera es componia línia a línia, omplint cada línia abans de passar a la següent, digués el que digués
optimalLineBreaking(vegeubodyText.optimalRaggeda Text de cos). - La divisió sota un encapçalament (regles 8): fins a la 1.4, el paràgraf que segueix un encapçalament al peu d'una columna hi conservava totes les línies que hi cabien, encara que en passessin molt poques a la columna següent (vegeu
headings.keepWithNextSplita Encapçalaments). - L'espai sota un contenidor
:::paragraphs(regles 8): fins a la 1.4, l'espai de l'estil es posava sota l'últim paràgraf abans de l'ajust a la retícula, l'espai del bloc següent (elmarginTopd'un encapçalament) se sumava a sota i la separació entre paràgrafs del text no comptava (vegeubodyText.paragraphContainerSpacinga Text de cos). - Els salts de línia després del guionet d'un compost (regles 8): fins a la 1.4, Knuth-Plass mai no acabava una línia justificada després d'un guionet entre dues lletres (
físico-química) en un paràgraf sense format en línia, i sí que ho feia en un amb format (vegeubodyText.breakAfterHyphensa Text de cos).
openBundle i readBundle llegeixen la config d'un manifest així, i la configuració de cada llengua a localized, a través de migrateConfig, que escriu els salts tal com els componia la 1.4 i multiplica l'escala de les fórmules per 1,131 (i divideix per aquest factor els marges de les fórmules en bloc donats en em). A un manifest marcat de 3 a 7, que va escriure una versió preliminar de la 1.5, només se li apliquen les fixacions de les regles posteriors a la seva marca: amb 3, la mida de les fórmules, l'espai en línia, les cinc fixacions de les regles 6, les dues de les regles 7 i les tres de les regles 8; amb 4, l'espai en línia i les de les regles 6, 7 i 8; amb 5, les de les regles 6, 7 i 8; amb 6, les de les regles 7 i 8; amb 7, només les de les regles 8. La divisió sota un encapçalament (pinLegacyHeadingSplit) s'escriu com a headings.keepWithNextSplit: 'fill' sobre el headings que deixen en vigor les capes, quan algun capítol llegit té un encapçalament i la configuració no anomena un valor propi, manté activat headings.keepWithNext i no desactiva bodyText.avoidOrphans. Els salts després del guionet d'un compost (pinLegacyHyphenBreaks) s'escriuen com a bodyText.breakAfterHyphens: false sobre el bodyText en vigor quan un capítol llegit té un guionet entre dues lletres i la configuració ni el fixa ja ni desactiva optimalLineBreaking. L'espai sota els contenidors (pinLegacyParagraphContainerSpacing) s'escriu com a bodyText.paragraphContainerSpacing: 'add' sobre el bodyText en vigor quan la configuració declara algun estil de paràgraf (a paragraphStyles o a la configuració pròpia del visor HTML), un capítol llegit obre un contenidor :::paragraphs en una línia pròpia i la configuració no el fixa ja. Els salts després d'un guió llarg (pinLegacyDashBreaks) s'escriuen com a bodyText.breakAfterDashes: false sobre el bodyText en vigor quan un capítol llegit té un guió llarg o un guió mitjà entre paraules sense espais (una lletra, una xifra o un signe de tancament davant, i una lletra, una xifra o un signe d'obertura darrere; unes cometes davant del guió compten quan les precedeix una lletra, una xifra, un signe de tancament o un espai de no separació, com a «no»—y, no a dijo "—Hola; una marca en línia enganxada al guió o a les cometes, com els ** de **I.**—Que, compta a qualsevol dels dos costats) i la configuració no el fixa ja. La composició del text en bandera (pinLegacyRaggedBreaking) s'escriu com a bodyText.optimalRagged: false sobre el bodyText en vigor quan la configuració posa en bandera algun text corregut (un textAlign diferent de 'justify' al text de cos, un estil de paràgraf, el cos d'un requadre, el d'una part o el d'un estil de secció (headingStyles[].bodyStyle), o a la configuració pròpia del visor HTML), no el fixa ja i no desactiva optimalLineBreaking. L'espai als requadres (pinLegacyBoxResourceGap) s'escriu com a layout.inlineResourceGapInBoxes: false sobre el layout en vigor quan un recurs s'insereix en una línia pròpia dins d'un :::callout dels capítols llegits i la configuració no el fixa ja. El tall de les caixes (pinLegacyBoxChildCut) s'escriu com a layout.boxChildSplitMinLines: 1 sobre el layout en vigor quan un capítol llegit obre un :::callout en una línia pròpia i la configuració no el fixa ja. Les marques dels encapçalaments (pinLegacyHeadingMarks) s'escriuen com a headings.inlineMarks: false sobre el headings que deixen en vigor les capes, quan algun encapçalament dels capítols llegits porta una marca (*, _, ^, ~, :smallcaps[ o un enllaç al títol) i la configuració no anomena un valor propi. A les caplletres (pinLegacyDropCapSize) se'ls escriu la mida de la 1.4 com a dropCap.fontSize, siguin on siguin: en la unitat de l'interlineat de l'element quan és una longitud, i si no, en la unitat del seu cos. El lloc sota els dos punts (pinLegacyColonListRoom) s'escriu com a bodyText.colonListRoom: 'line' sobre el bodyText en vigor quan una llista dels capítols llegits segueix una línia que acaba en dos punts (encara que hi hagi línies en blanc entre elles) i la configuració ni anomena un lloc ni desactiva keepColonWithList. L'espai en línia (pinLegacyInlineGap) s'escriu com a layout.inlineResourceGap: 'above' sobre el layout que deixen en vigor les capes, quan una línia dels capítols llegits insereix un recurs (::resource{id="…"} sola a la seva línia, tal com la llegeix l'analitzador: una menció al text o en un fragment de codi no compta) i la configuració no anomena un espai propi. La mida es fixa sobre el math que deixen en vigor les capes (el math propi d'una llengua substitueix el compartit), i només quan els capítols llegits tenen algun $: un paquet sense fórmules conserva la seva config tal com es va escriure. Si ni el manifest ni la llengua donen un math, el que queda en vigor és el de la baseConfig de readBundle (la del lector), i també es fixa, perquè la 1.4 componia les fórmules del paquet a aquesta mida: una base amb fontSizeScale: 1.5 es llegeix com a 1,5 × 1,1312. Els salts d'encapçalament de la base es prenen tal com vénen. Així un paquet antic conserva el que componien aquestes regles, i bundle.config mostra els salts, la mida de les fórmules, els espais, les marques dels encapçalaments, la mida de les caplletres, el lloc sota els dos punts, el tall de les caixes, els salts després d'un guió llarg, els salts després del guionet d'un compost, la composició del text en bandera, la divisió sota un encapçalament i l'espai sota els contenidors amb què es compon. Les correccions de composició de la 1.5 no tenen fixació i se li apliquen com a qualsevol llibre, així que una pàgina a la qual afectin encara es pot moure (la llista és a Mida de les fórmules). Un preset.json escrit a mà per a les regles actuals posa "configVersion": 8; marcar així el manifest d'un paquet antic és també la manera de llegir-lo, amb una línia, amb les regles actuals (un paquet sense versió perd llavors també la fixació dels seus salts d'encapçalament).
import { CONFIG_VERSION, migrateConfig } from 'postext/bundle';
migrateConfig({ headings: { fontFamily: 'Georgia' } }, undefined, { content: 'Un llibre sense fórmules.' });
// => { headings: { fontFamily: 'Georgia', levels: [{ level: 1, breakBefore: { enabled: false } }] } }
migrateConfig({ math: { fontSizeScale: 1.2 } }, 3);
// => { math: { fontSizeScale: 1.35746…, marginTop: { value: 0.7072, unit: 'em' }, marginBottom: { value: 0.7072, unit: 'em' } },
// layout: { inlineResourceGap: 'above', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 },
// headings: { inlineMarks: false, keepWithNextSplit: 'fill' }, bodyText: { colonListRoom: 'line', breakAfterDashes: false, breakAfterHyphens: false } }
migrateConfig({ layout: { layoutType: 'single' } }, 4, { content: 'Text.\n\n::resource{id="fig"}' });
// => { layout: { layoutType: 'single', inlineResourceGap: 'above' } }
migrateConfig({ layout: { layoutType: 'single' } }, 5, { content: ':::callout\nText.\n\n::resource{id="fig"}\n:::' });
// => { layout: { layoutType: 'single', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 } }
migrateConfig({ bodyText: { textAlign: 'left' } }, 6, { content: 'Et dic—i això és tot.' });
// => { bodyText: { textAlign: 'left', breakAfterDashes: false, optimalRagged: false } }
migrateConfig({ paragraphStyles: [{ id: 'verso' }] }, 7, { content: ':::paragraphs{style="verso"}\nUn vers.\n:::' });
// => { paragraphStyles: [{ id: 'verso' }], bodyText: { paragraphContainerSpacing: 'add' } }
migrateConfig({ bodyText: { fontFamily: 'Georgia' } }, 7, { content: 'Un exercici teòric-pràctic.' });
// => { bodyText: { fontFamily: 'Georgia', breakAfterHyphens: false } }
migrateConfig(config, CONFIG_VERSION); // regles actuals: la mateixa `config`content és el markdown que compon la configuració (una cadena o una llista de capítols). Sense aquest camp, els dos espais, les marques dels encapçalaments, el lloc sota els dos punts, el tall de les caixes, els salts després d'un guió llarg, la divisió sota un encapçalament i els salts després del guionet d'un compost es fixen sempre, la mida de les fórmules sempre que les matemàtiques estiguin activades i l'espai sota els contenidors sempre que la configuració declari algun estil de paràgraf, perquè el motor no pot saber si el llibre té cap fórmula, cap contenidor :::paragraphs, cap figura en línia, cap encapçalament amb marques, cap llista introduïda amb dos punts, cap caixa, cap guió llarg entre paraules, cap encapçalament o cap compost. La composició del text en bandera es fixa només segons la configuració, hi hagi contingut o no. Migra una configuració desada una sola vegada i torna-la a desar amb CONFIG_VERSION: la fixació de la mida multiplica l'escala, així que una configuració migrada dues vegades creixeria dues vegades.
#Compondre i renderitzar un paquet
Quatre utilitats connecten un paquet obert amb el motor i els backends:
loadBundleFonts(bundle)registra les fonts del paquet adocument.fonts. Espera-la abans de compondre, perquè la composició mesura el text amb les fonts que té el navegador. Les famílies que el paquet anomena però no inclou (Google Fonts) les has de carregar tu, com en qualsevol altre document.registerBundleImages(bundle)descodifica les imatges per al backend canvas (renderPage,renderToCanvas).bundleImageUrl(bundle)és el resolvedorresourceImageUrlderenderToHtml. Totes dues recoloren les figures SVG quandiagramStyle.singleInkés actiu, una sola vegada: recoloren el marcatge i marquen les imatges perquè cap backend no les torni a tenyir (consulta Tinta única en canvas i en HTML).buildBundle(bundle)compon els capítols en ordre i retorna unVDTDocumentper capítol. Cada capítol continua l'anterior: comptadors d'encapçalaments i de recursos, la part oberta, la paritat de pàgina i la numeració. Un capítol que imprimeix l'índex de continguts (:::toc) o l'analític (:::index) rep l'esquema del llibre sencer. Admet les mateixes opcions quebuildDocument, mésconfigper substituir la configuració del paquet,cacheper compartir una memòria cau de mesures imetadata(vegeu més avall).bundleResourceBytes(bundle)ibundleFontProvider(bundle, { decodeWoff2, fallback })són les opcionsresourceBytesifontProviderderenderToPdfdepostext-pdf. El proveïdor de fonts tria del paquet el pes més proper de l'estil demanat. Per a una variant.woff2necessitadecompressWoff2, i per a una família que el paquet no inclou cridafallbackamb els arguments del renderitzador,requestinclòs, i lliura el que retorni. Unfallbackque només baixa el fitxerlatinde la família imprimeix com a caixes buides una família xinesa que el paquet no incrusta; un que respon amb els fragments, comsliceFontProvidera Fonts xineses, japoneses i coreanes, la imprimeix completa.
import { openBundle, loadBundleFonts, registerBundleImages, buildBundle, renderPage,
bundleResourceBytes, bundleFontProvider } from 'postext';
import { renderToPdf, decompressWoff2 } from 'postext-pdf';
const bundle = await openBundle(bytes);
await loadBundleFonts(bundle);
await registerBundleImages(bundle);
const docs = buildBundle(bundle); // un VDTDocument per capítol
const primeraPagina = renderPage(docs[0].pages[0], docs[0]); // un <canvas>
const pdf = await renderToPdf(docs, { // el llibre sencer
fontProvider: bundleFontProvider(bundle, { decodeWoff2: decompressWoff2, fallback: fontsource }),
resourceBytes: bundleResourceBytes(bundle),
});Per compondre un sol capítol pel teu compte, passa bundle.chapters[i].markdown, bundle.resources i bundle.config a buildDocument, com amb qualsevol document.
Les metadades del llibre. Com al Sandbox, el frontmatter del primer capítol és el del llibre: buildBundle lliura el seu title, el seu author i els altres a tots els capítols, de manera que les capçaleres amb {title} i {author} es mantenen a totes les pàgines i el doc.metadata de cada capítol els porta. Un bloc de frontmatter al principi d'un capítol posterior s'ignora: es localitza per les seves línies --- i es deixa en blanc sense analitzar-lo, així que un YAML que l'analitzador rebutjaria no causa cap problema. options.metadata aporta valors que el frontmatter no fixa (el frontmatter preval). El recompte de pàgines del llibre també arriba a tots els capítols: {bookTotalPages} l'imprimeix, mentre que {totalPages} compta el capítol (consulta Recompte de pàgines del llibre).
const docs = buildBundle(bundle, { metadata: { author: 'A. Autora' } });
docs[3].metadata.title; // el `title:` del primer capítol#Exemple en directe: obrir un paquet
El pen carrega un llibre d'exemple de dos capítols (lantern.postext, amb la seva pròpia tipografia, una figura SVG i una taula) des del repositori. Registra les fonts i imatges del paquet, compon el llibre amb buildBundle i pinta totes les pàgines. Make the PDF renderitza els mateixos documents amb postext-pdf, incrustant les fonts del paquet. Tria un .postext teu, per exemple un d'exportat del Sandbox, per veure'l igual.
import {
openBundle,
loadBundleFonts,
registerBundleImages,
buildBundle,
bundleResourceBytes,
bundleFontProvider,
renderPage,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
// A two-chapter book with its own typeface, an SVG figure and a table.
const SAMPLE = 'https://cdn.jsdelivr.net/gh/drnachio/postext@main/docs/examples/open-bundle/lantern.postext';
const status = document.getElementById('status');
const pdfButton = document.getElementById('pdf');
let current = null;
async function show(data) {
// Chapters, config (fonts wired to the bundle's own files), resources and
// every file, keyed by its path inside the bundle.
const bundle = await openBundle(data);
// Layout measures text with the fonts the browser has: register the
// bundle's faces, and load the Google Fonts it names but does not carry
// (the default running heads use Open Sans; see the pen's CSS).
await loadBundleFonts(bundle);
await document.fonts.load('600 16px "Open Sans"');
await registerBundleImages(bundle);
// One VDTDocument per chapter, each continuing the one before it.
const docs = buildBundle(bundle);
const pages = docs.flatMap((doc) => doc.pages.map((page) => renderPage(page, doc)));
document.getElementById('pages').replaceChildren(...pages);
status.textContent = `${bundle.name} · ${bundle.chapters.length} chapter(s) · ${pages.length} page(s)`
+ (bundle.warnings.length ? ` · ${bundle.warnings.length} warning(s)` : '');
current = { bundle, docs };
pdfButton.disabled = false;
document.getElementById('links').hidden = true;
}
// Fonts the bundle does not carry come from Fontsource.
async function fontsource(family, weight, style) {
const id = family.toLowerCase().replace(/\s+/g, '-');
const res = await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`);
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${family}`);
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
}
pdfButton.addEventListener('click', async () => {
pdfButton.disabled = true;
status.textContent = 'Rendering the PDF…';
const { bundle, docs } = current;
const bytes = await renderToPdf(docs, {
fontProvider: bundleFontProvider(bundle, { decodeWoff2: decompressWoff2, fallback: fontsource }),
resourceBytes: bundleResourceBytes(bundle),
});
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
document.getElementById('open').href = url;
document.getElementById('download').href = url;
document.getElementById('links').hidden = false;
status.textContent = `${bundle.name} · ${(bytes.length / 1024).toFixed(0)} KB PDF`;
pdfButton.disabled = false;
});
document.getElementById('file').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
status.textContent = `Opening ${file.name}…`;
await show(file).catch((err) => { status.textContent = `Could not open ${file.name}: ${err.message}`; });
});
const res = await fetch(SAMPLE);
await show(await res.arrayBuffer());index.html
<p>
<label>Open a .postext file: <input id="file" type="file" accept=".postext,application/zip"></label>
<button id="pdf" disabled>Make the PDF</button>
<span id="links" hidden>
<a id="open" target="_blank" rel="noopener">open it</a> ·
<a id="download" download="book.pdf">download it</a>
</span>
</p>
<p id="status">Loading the sample book…</p>
<div id="pages"></div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#pages {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: flex-start;
}
#pages canvas {
display: block;
width: 240px;
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Crear un paquet
createBundle escriu un fitxer .postext a partir d'un document: els seus capítols, la seva configuració, els seus recursos i les dades a què aquests fan referència.
import { createBundle } from 'postext';
const { bytes, manifest, warnings } = await createBundle({
name: 'El farol',
locale: 'es',
chapters: [
{ markdown: '# Anochecer\n\nSe dibuja en :ref{id="farol"}.' },
{ title: 'Noche', markdown: '# Noche\n\n…' },
],
config,
resources: [{
id: 'farol', typeId: 'figure', kind: 'svg', caption: 'El farol.',
svg: { fileId: 'farol.svg', width: 240, height: 150 },
createdAt: 0, updatedAt: 0,
}],
files: { 'farol.svg': svgMarkup, 'garamond-regular': fontBytes },
});| Entrada | Significat |
|---|---|
name, id, description, locale | Les metadades del manifest. Per defecte id és un slug de name. |
chapters o markdown | El llibre, un { title?, markdown } per capítol, o un document únic. |
config | La PostextConfig. Els valors iguals als predeterminats no s'escriuen al manifest. |
resources | Els recursos. Una imatge anomena les seves dades amb bitmap.fileId / svg.fileId (i svg.pdfFileId per a un màster d'impressió). |
files | Les dades per fileId (un objecte o un Map): les imatges que citen els recursos i els fitxers de font que citen les variants de config.customFonts. Cada valor pot ser un Uint8Array, un ArrayBuffer, un Blob o un text (el codi d'un SVG). |
thumbnail | { data, mime }: una portada (PNG, JPEG, WebP, GIF o SVG). |
canvasScope | 'book' demana als visors que componguin el llibre sencer com un sol llenç. |
mtime | La data de modificació que s'escriu a cada fitxer del zip (un Date, una marca de temps o una data en text). Si falta, és l'hora de la crida, de manera que dues crides amb les mateixes dades donen bytes diferents. Amb una data fixa, les mateixes dades donen sempre els mateixos bytes, que es poden comparar o resumir amb un hash. Un zip desa una data i una hora sense zona horària, en passos de dos segons, de 1980 a 2099, i la data s'escriu en l'hora local de la màquina. Perquè els bytes coincideixin en qualsevol màquina, construeix la data amb camps locals, com new Date(1980, 0, 1): una marca de temps o un text que acaba en Z anomenen un instant, que cau en una hora local diferent a cada zona horària ('1980-01-01T00:00:00Z' encara és 1979 a l'oest d'UTC). Una data fora d'aquests anys, en hora local, llança un error. |
localized | Altres llengües del mateix llibre, per etiqueta de llengua: { en: { chapters?, config?, resources? } }. Les entrades anteriors passen a ser el contingut de locale, que llavors és obligatori. Consulta Paquets bilingües. |
Retorna els bytes del fitxer, el manifest escrit com a preset.json, tots els fitxers a files (ruta → bytes) i una llista de warnings. Els fitxers s'anomenen per l'id del seu recurs (resources/farol.svg) o pel nom del fitxer de font (fonts/…), i els capítols pel seu ordre i títol (chapters/01-anochecer.md). Les fonts es declaren al camp fonts del manifest, mai dins de config.customFonts. Algunes coses es deixen fora, cadascuna amb un avís:
- un recurs o una variant tipogràfica les dades de la qual no són a
files - una variant
.woff(el backend PDF no la pot incrustar) - una família marcada
redistributable: false
Al navegador, passa bytes a un enllaç de baixada: URL.createObjectURL(new Blob([bytes], { type: 'application/zip' })). A Node, escriu-los amb fs.writeFile. createBundle i openBundle no necessiten DOM. Els dists fan servir rutes de mòdul sense extensió, de manera que amb Node a seques, sense bundler, necessiten un hook de resolució. El fitxer docs/examples/open-bundle/build-sample.mjs del repositori en mostra un en poques línies.
#Paquets bilingües
Un fitxer .postext pot portar un llibre en diverses llengües, i openBundle(bytes, { locale }) el llegeix en qualsevol d'elles. createBundle n'escriu un a partir de localized: una entrada per cada llengua addicional, amb allò que difereix del contingut principal (el de locale):
const { bytes, manifest } = await createBundle({
name: 'El farol',
locale: 'es',
chapters: [{ markdown: '# Anochecer\n\n…' }, { markdown: '# Noche\n\n…' }],
config,
resources: [figuraFarol, tablaHoras],
files: { 'farol.svg': svgEs, 'lantern.svg': svgEn },
localized: {
en: {
chapters: [{ markdown: '# Dusk\n\n…' }, { markdown: '# Night\n\n…' }],
config: { headings: { levels: [{ level: 1, numberingTemplate: 'Chapter {1}' }] } },
resources: [
{ id: 'farol', caption: 'The lantern.', svg: { fileId: 'lantern.svg', width: 240, height: 150 } },
{ id: 'horas', caption: 'Hours of light.' },
],
},
},
});
const en = await openBundle(bytes, { locale: 'en' }); // capítols, configuració i peus en anglèschapters: el llibre en aquesta llengua. Els fitxers de capítol van a una carpeta per llengua (chapters/es/01-anochecer.md,chapters/en/01-dusk.md) i el campchaptersdel manifest passa a ser un mapa llengua → capítols. Una llengua sensechaptersllegeix els principals; quan cap llengua no té capítols propis, continuen sent una sola llista.config: la configuració d'aquesta llengua. Cada clau de primer nivell substitueix completament la compartida quan el paquet es llegeix en aquesta llengua, de manera que aquíheadingssubstitueix l'objecteheadingssencer. Les claus que falten, o que són iguals a les compartides, es comparteixen i no s'escriuen, de manera que passar la configuració completa de la llengua funciona igual que passar les poques claus que canvien. Una clau amb els seus valors per defecte quan la compartida no els té (layout: {}) s'escriu tal qual, així que restableix el valor compartit. Les fonts es comparteixen: les famílies delcustomFontsd'una llengua se sumen alfontsdel paquet.resources: el text dels recursos compartits, aparellats perid:caption,note,altTexti eltabled'una taula. Una imatge amb paraules pot tenir el seu propi dibuix:bitmap.fileIdosvg.fileId(isvg.pdfFileId) anomenen altres dades defiles, que s'escriuen com aresources/en/farol.svg. La resta de camps, com el tipus o la col·locació, es comparteixen. Un id que no és entre elsresourceses deixa fora amb un avís, i si falta la imatge d'una llengua, aquesta llengua es queda amb la compartida, també amb un avís.
El manifest enumera totes les llengües a locales (['es', 'en']), conserva la principal com a locale i desa la resta a localized. openBundle sense llengua llegeix la principal.
Quina llengua rep el lector. openBundle(bytes, { locale }) serveix la llengua exacta, si no la seva llengua base (es-MX llegeix es), i si no la principal; bundle.locale diu quina ha servit. Capítols i textos vénen sempre de la mateixa llengua. La llengua principal conserva els textos compartits encara que localized porti una variant regional seva: un paquet pt-PT amb una entrada pt-BR llegeix els peus brasilers només per a pt-BR, i els compartits per a pt-PT i pt.
#Exemple en viu: crear un paquet
El pen compon un llibre de dos capítols amb una figura SVG i llista els fitxers que ha escrit createBundle juntament amb el manifest. Ofereix el fitxer per baixar-lo, el torna a obrir amb openBundle i en pinta la primera pàgina: l'anada i tornada completa en poques línies. Importa el fitxer baixat al Sandbox per continuar-hi treballant.
import { createBundle, openBundle, registerBundleImages, buildBundle, renderPage } from 'https://esm.sh/postext';
// A picture resource names its payload by fileId; the bytes (here, SVG
// markup) go in `files` under that same id.
const lanternSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 150">
<rect width="240" height="150" fill="#f3efe6"/>
<path d="M100 36 h40 l8 14 h-56 z" fill="#2f3e46"/>
<rect x="98" y="50" width="44" height="58" rx="4" fill="#f6c453" stroke="#2f3e46" stroke-width="4"/>
<circle cx="120" cy="79" r="11" fill="#fff4c2"/>
<path d="M94 108 h52 l-6 12 h-40 z" fill="#2f3e46"/>
</svg>`;
const resources = [{
id: 'lantern',
typeId: 'figure',
kind: 'svg',
caption: 'The lantern by the door.',
svg: { fileId: 'lantern.svg', width: 240, height: 150 },
createdAt: 0,
updatedAt: 0,
}];
const text = 'The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.';
// One entry per chapter; a chapter without a title takes its first # heading.
const chapters = [
{ markdown: `# Dusk\n\n${text} It is drawn in :ref{id="lantern"}.\n\n${text}\n\n${text}` },
{ markdown: `# Night\n\n${text}\n\n${text}` },
];
const config = {
layout: { layoutType: 'double' },
// Two short chapters that run on, with no blank verso between them (an
// H1 otherwise opens on a fresh recto), as in the open-bundle sample.
headings: { levels: [{ level: 1, numberingTemplate: 'Chapter {1}', breakBefore: { enabled: false } }] },
};
// Everything a .postext file holds: manifest, chapters, resources, fonts.
const { bytes, manifest, files, warnings } = await createBundle({
name: 'The Lantern',
locale: 'en',
chapters,
config,
resources,
files: { 'lantern.svg': lanternSvg },
});
if (warnings.length) console.warn(warnings);
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }));
document.getElementById('download').href = url;
document.getElementById('actions').hidden = false;
document.getElementById('files').replaceChildren(...Object.entries(files).map(([path, data]) => {
const li = document.createElement('li');
li.textContent = `${path} (${data.length} B)`;
return li;
}));
document.getElementById('manifest').textContent = JSON.stringify(manifest, null, 2);
// Round trip: open the file just written, the way any program would.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const bundle = await openBundle(bytes);
await registerBundleImages(bundle);
const [firstChapter] = buildBundle(bundle);
document.getElementById('page').replaceChildren(renderPage(firstChapter.pages[0], firstChapter));
document.getElementById('status').textContent =
`${bundle.name}: ${bundle.chapters.length} chapters, ${(bytes.length / 1024).toFixed(1)} KB`;index.html
<p id="status">Building the bundle…</p>
<p id="actions" hidden>
<a id="download" download="lantern.postext">Download lantern.postext</a> ·
<a href="https://postext.dev/en/sandbox" target="_blank" rel="noopener">open the Sandbox</a> and import it (Projects → New → Import .postext…)
</p>
<div id="output">
<section>
<h3>Files in the bundle</h3>
<ul id="files"></ul>
<h3>preset.json</h3>
<pre id="manifest"></pre>
</section>
<section>
<h3>Opened again: page 1</h3>
<div id="page"></div>
</section>
</div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#output {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: flex-start;
}
#output section {
flex: 1 1 280px;
min-width: 0;
}
h3 {
margin: 8px 0;
font-size: 14px;
}
ul {
margin: 0;
padding-left: 20px;
font-family: ui-monospace, monospace;
font-size: 13px;
}
pre {
max-height: 320px;
overflow: auto;
padding: 8px;
background: #fff;
font-size: 12px;
}
#page canvas {
display: block;
max-width: 100%;
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}Carrega un editor interactiu des de codepen.io. L'exemple importa l'última versió publicada de postext des d'un CDN.
#Treballar amb paquets
Com que el Sandbox, l'skill per a agents i el paquet postext llegeixen i escriuen el mateix fitxer, un .postext és una manera còmoda de passar un llibre d'una eina a una altra:
- Partir d'un paquet. Porta una publicació existent amb l'skill per a agents, o dissenya un llibre al Sandbox i exporta'l (Baixar (.postext) al menú ⋯ de la seva fila del tauler Llibres). Carrega el fitxer des del teu programa amb
openBundleper renderitzar-lo en canvas, HTML o PDF. Conserva el fitxer com a font del llibre: edita en codi els capítols, la configuració o els recursos i torna'l a escriure ambcreateBundle, o simplement torna'l a carregar cada vegada que canviï. - Depurar i ajustar al Sandbox. Quan alguna cosa del que produeix el teu programa necessita retocs (una figura que cau a la pàgina equivocada, un estil d'encapçalament, l'equilibri de columnes), exporta el que compon el teu programa amb
createBundle. Importa aquest fitxer al Sandbox (Llibres → Nou → Obrir un fitxer .postext…), corregeix el text, el disseny o les figures amb la previsualització en viu, el tauler Revisió i la vista PDF, i torna'l a exportar. Després el teu programa carrega el fitxer corregit ambopenBundle. O trasllada al teu codi el que ha canviat: laconfigdel manifest només desa els valors diferents dels predeterminats, així que es llegeix com una diferència curta.
#API de baix nivell
postext/bundle exporta també les peces sobre les quals es construeixen openBundle i createBundle, per a aplicacions que desen o serveixen paquets a la seva manera (un directori descomprimit per HTTP, registres en una base de dades):
openBundleZip(bytes)/zipBundle(files, { mtime }): la capa del zip. En obrir tolera una carpeta arrel i ignora les entrades__MACOSXi els fitxers ocults. Es rebutgen les rutes que surten del paquet.mtimedata els fitxers com la dada del mateix nom decreateBundle.readBundle(manifest, readFile, options)llegeix un manifest més una funcióreadFile(ruta)i retorna capítols, configuració, recursos, imatges i fonts.optionsfixa la llengua, com s'anomenen els identificadors de fitxer (ids), la configuració base (baseConfig, sota la del manifest; per defecte la paleta i els tipus de recurs debundleBaseConfigen la llengua del paquet, que retornaresolveBundleConfigLocale(manifest, locale), i qui passi la seva pròpiabaseConfigl'ha de traduir a aquesta llengua; amb un manifest anterior aconfigVersion: 4, el seumathes fixa amb el del paquet; amb un d'anterior a 5, l'espai en línia del seulayout; amb un d'anterior a 6, l'espai als requadres del seulayout, el lloc sota els dos punts del seubodyText, les marques en línia dels seusheadingsi la mida de les seves caplletres; amb un d'anterior a 7, els talls després d'un guió llarg i la composició del text en bandera del seubodyText; i amb un d'anterior a 8, la divisió sota un encapçalament dels seusheadingsi els talls després del guionet d'un compost i l'espai sota els contenidors:::paragraphsdel seubodyText; vegeu Paquets escrits per postext 1.4 o anterior) i com es mesuren les mides intrínseques.planBundle(meta, content)/resolveBundleFiles(plan, sources): el costat d'escriptura, separat en un pla pur (noms de fitxer i manifest) i la resolució dels bytes mitjançant les funcionsreadBlob/readFont.isBundleManifest(value), les funcions que trien llengua (pickChapterSpecs,pickLocaleOverrides,pickBundleView,resolveBundleLocale,resolveBundleConfigLocale),svgSize/bitmapSizei els tipus del format (BundleManifest,BundleResourceSpec,BundleFontFamilySpec, …).CONFIG_VERSION,migrateConfig(config, configVersion, { content }),pinLegacyHeadingBreaks(config),pinLegacyMathSize(config),pinLegacyInlineGap(config),pinLegacyBoxResourceGap(config),pinLegacyHeadingMarks(config),pinLegacyDropCapSize(config),pinLegacyColonListRoom(config),pinLegacyBoxChildCut(config),pinLegacyDashBreaks(config),pinLegacyRaggedBreaking(config),pinLegacyHeadingSplit(config),pinLegacyParagraphContainerSpacing(config),pinLegacyHyphenBreaks(config)iLEGACY_MATH_SIZE(0,5 ÷ 0,442): una configuració desada, en els termes actuals (vegeu Paquets escrits per postext 1.4 o anterior).readBundlel'aplica; una aplicació que desa configuracions a la seva manera també ho pot fer, una vegada per còpia desada.
El Sandbox està construït sobre aquestes peces. Hi afegeix els seus propis identificadors d'emmagatzematge i els registres de pàgines de layouts.json.