# Prosa clássica vocalizada e os parênteses corânicos

> Uma maqāma com vogal em cada letra, composta com uma entrelinha do dobro do corpo para que os sinais de uma linha não encostem nos da linha seguinte.

- Versão HTML: https://postext.dev/pt/cookbook/vocalised-classical-prose
- Receita Nº 111 · Tipo e texto · Nível 1 (Básico) · Saídas: Canvas
- Gêneros: Ficção, teatro e prosa literária
- Requer postext ≥ 1.15.0 · testada com 1.19.1 em 2026-10-06
- Páginas: [١](https://postext.dev/cookbook/vocalised-classical-prose/en/p01.webp?v=e2100c7f), [٢](https://postext.dev/cookbook/vocalised-classical-prose/en/p02.webp?v=e2100c7f), [٣](https://postext.dev/cookbook/vocalised-classical-prose/en/p03.webp?v=e2100c7f), [٤](https://postext.dev/cookbook/vocalised-classical-prose/en/p04.webp?v=e2100c7f)
- Abrir no Sandbox: https://postext.dev/pt/sandbox#recipe=vocalised-classical-prose&lang=en (.postext: https://postext.dev/cookbook/vocalised-classical-prose/en/vocalised-classical-prose.postext)
- Última atualização: 2026-10-04
- Outros idiomas: [en](https://postext.dev/en/cookbook/vocalised-classical-prose.md), [es](https://postext.dev/es/cookbook/vocalised-classical-prose.md), [ca](https://postext.dev/ca/cookbook/vocalised-classical-prose.md), [zh](https://postext.dev/zh/cookbook/vocalised-classical-prose.md), [ja](https://postext.dev/ja/cookbook/vocalised-classical-prose.md), [ar](https://postext.dev/ar/cookbook/vocalised-classical-prose.md)

## Em poucas palavras

Uma história árabe antiga impressa com todos os sinaizinhos de vogal em cima e embaixo das letras. As linhas ficam afastadas o bastante para que os sinais de uma não batam nos da linha de baixo.

## O que você vai compor

Quatro páginas de um livro árabe clássico em que cada letra leva a sua vogal: a *Maqāma do hospício*, de al-Hamadhānī, em que um louco do hospício de Basra deixa um teólogo sem resposta e depois diz o próprio nome em verso. O texto com todas as vogais empilha fatḥa e shadda em cima das letras e kasra embaixo, e esses sinais moram no espaço entre as linhas. Por isso a página é composta em Amiri, 14 pt sobre 28 pt, o dobro do corpo, e mais nada: a entrelinha é o projeto gráfico. O texto é justificado com kashidas, abre com um recuo de 1,5 em à direita e põe as suas três citações do Corão entre os parênteses ornamentais ﴿ ﴾ do muṣḥaf impresso. Só a linha latina do título e a nota sobre o texto mudam com a edição.

**Esta receita responde a:**

- Quanta entrelinha precisa o árabe com todas as vogais marcadas?

## A resposta curta

```js
// script.js, linhas 28–45
// Every letter of this maqāma carries its vowel: fatḥa and shadda over the letters, kasra
// and kasratān under them, often two marks on one letter. The marks sit in the leading,
// which never grows for them, so the leading is chosen for the most vocalised line of the
// book. Unvocalised prose sets well at 1.6 × the size; fully vocalised text needs
// 1.9–2.1 ×: here 14 pt on 28 pt, 2 ×, so a kasra hanging under one line clears a shadda
// standing on the line below. Any closer and the build reports the paragraph
// (arabicMarksExceedLeading) with the leading it would need.
const SIZE = 14; // pt
const LEAD = 2 * SIZE; // pt: 28
const bodyText = {
  fontFamily: NASKH, fontSize: pt(SIZE), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  // Justified with kashidas, the default in an Arabic book, written out to say so:
  // a line widens at its joins as well as at its spaces.
  textAlign: 'justify', kashida: 'auto', kashidaPatterns: 'naskh',
  firstLineIndent: em(1.5), indentAfterHeading: false, // on the start side, the right
  optimalLineBreaking: true, avoidWidows: true, avoidOrphans: true,
};
```

## Ingredientes

**Ensina**

- [Fontes árabes](https://postext.dev/pt/docs/arabic-layout.md#fontes): As fontes árabes do Fontsource carregam o arquivo arabic ao lado dos latin, na tela e no PDF, que dá forma às letras com HarfBuzz a partir dos mesmos bytes: formas de ligação, ligaduras e os harakat onde a fonte os posiciona.
- [Tipografia do texto](https://postext.dev/pt/docs/configuration.md#texto-do-corpo): O tipo do texto, seu corpo, sua entrelinha e seus pesos; a entrelinha é também o passo da grade de linhas de base.

**Também usa**

- [Texto da direita para a esquerda](https://postext.dev/pt/docs/arabic-layout.md#direção-e-algoritmo-bidirecional)
- [Árabe como idioma do documento](https://postext.dev/pt/docs/arabic-layout.md#como-começar-um-livro-em-árabe)
- [Recuos, alinhamento e espaço entre parágrafos](https://postext.dev/pt/docs/configuration.md#texto-do-corpo)
- [Justificação com kashida](https://postext.dev/pt/docs/justification.md#kashida-no-texto-árabe)
- [Verso árabe (bayt em dois hemistíquios)](https://postext.dev/pt/docs/document-format.md#verse)
- [Livros encadernados pela direita](https://postext.dev/pt/docs/configuration.md#encadernação)
- [Parágrafos na outra direção](https://postext.dev/pt/docs/arabic-layout.md#direção-do-documento-do-bloco-e-do-trecho)
- [Níveis de título](https://postext.dev/pt/docs/configuration.md#sobrescritas-por-nível)
- [Formato da página](https://postext.dev/pt/docs/configuration.md#predefinições-de-tamanho-de-página)
- [Margens espelhadas](https://postext.dev/pt/docs/configuration.md#margens-espelhadas)
- [Cor do papel](https://postext.dev/pt/docs/configuration.md#página)
- [Paleta de cores semântica](https://postext.dev/pt/docs/configuration.md#paleta-de-cores)
- [Páginas em um canvas](https://postext.dev/pt/docs/configuration.md#renderizar-uma-página-como-bitmap)
- [Algarismos dos números gerados](https://postext.dev/pt/docs/configuration.md#idioma-do-documento)
- [Atributos de título](https://postext.dev/pt/docs/document-format.md#atributos-de-título)
- [Estilos de parágrafo](https://postext.dev/pt/docs/configuration.md#estilos-de-parágrafo)

**A configuração em resumo**

- [`bodyText`](https://postext.dev/pt/docs/configuration.md#texto-do-corpo), [`colorPalette`](https://postext.dev/pt/docs/configuration.md#paleta-de-cores), [`footer`](https://postext.dev/pt/docs/configuration.md#cabeços-e-rodapés), [`header`](https://postext.dev/pt/docs/configuration.md#cabeços-e-rodapés), [`headings`](https://postext.dev/pt/docs/configuration.md#títulos), [`layout`](https://postext.dev/pt/docs/configuration.md#diagramação), [`locale`](https://postext.dev/pt/docs/configuration.md#hifenização), [`page`](https://postext.dev/pt/docs/configuration.md#página)

**API**

- [`buildDocument`](https://postext.dev/pt/docs/configuration.md#compilar-um-documento), [`clearMeasurementCache`](https://postext.dev/pt/docs/configuration.md#cache-de-medidas), [`renderPageToCanvas`](https://postext.dev/pt/docs/configuration.md#renderizar-uma-página-como-bitmap)

**Tipos**

- Amiri (OFL-1.1), Aref Ruqaa (OFL-1.1)

## Preparo

### 1 · Uma entrelinha do dobro do corpo

O código é [a resposta curta](https://postext.dev/pt/cookbook/vocalised-classical-prose.md#a-resposta-curta) acima. A entrelinha nunca cresce para abrir espaço para os sinais: a kasra de uma linha e a shadda da seguinte dividem o mesmo vão, então o vão é escolhido pela linha mais vocalizada do livro ([Sinais vocálicos](https://postext.dev/pt/docs/arabic-layout.md#sinais-vocálicos)). Neste texto, uma entrelinha de 1,6 vez o corpo deixa seis parágrafos com sinais que se chocam, e 1,9 ainda deixa um; com 2, nenhum. A composição avisa: quando duas linhas se chocam, ela aponta o parágrafo com `arabicMarksExceedLeading` e a entrelinha de que ele precisaria ([Texto árabe](https://postext.dev/pt/docs/configuration.md#texto-árabe)).

![Page ٢.](https://postext.dev/cookbook/vocalised-classical-prose/en/p02.webp?v=e2100c7f)

*Página 2: uma vogal em cada letra e 28 pt entre as linhas de base, de modo que as kasras de uma linha passam livres das shaddas da seguinte.*

### 2 · Uma página pequena com uma medida larga

```js
// script.js, linhas 49–56
// 166 mm of text block hold seventeen lines on the 28 pt pitch (9.88 mm). The measure is
// 106 mm, about twelve words of vocalised Naskh.
const page = {
  sizePreset: 'custom', width: mm(140), height: mm(210), dpi: 150,
  backgroundColor: col('paper'),
  // mirror: left is the inner margin, at the spine (the right edge of an odd page here)
  margins: { top: mm(22), bottom: mm(22), left: mm(18), right: mm(16), mirror: true },
};
```

Uma página de 14 × 21 cm com margens laterais de 16 e 18 mm deixa uma medida de 106 mm, umas doze palavras vocalizadas, e dezessete linhas no passo de 28 pt. Os sinais deixam as linhas acinzentadas, então a página fica sóbria: sem cabeços, papel creme e um único título em vermelho. `mirror: true` põe a margem mais larga na lombada, que num livro encadernado pela direita é a borda direita de uma página ímpar ([Ordem das páginas e encadernação pela direita](https://postext.dev/pt/docs/arabic-layout.md#ordem-das-páginas-e-encadernação-à-direita)).

### 3 · Títulos que respeitam a grade

```js
// script.js, linhas 60–75
// The headings carry no design: the title's line box, three lines of the grid deep, lowers
// it on the page (a top margin is dropped at the head of a page), and the levels under it
// keep to the 28 pt pitch, so the text starts on a line of the grid.
const headings = {
  fontFamily: NASKH, color: col('ink'), textAlign: 'center',
  levels: [
    // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break).
    { level: 1, fontFamily: RUQAA, fontWeight: 700, fontSize: pt(32), lineHeight: pt(LEAD * 3),
      color: col('rubric'), marginTop: pt(0), marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontWeight: 700, fontSize: pt(15), lineHeight: pt(LEAD), color: col('ink'),
      marginTop: pt(0), marginBottom: pt(0) },
    { level: 3, fontWeight: 400, fontSize: pt(10), lineHeight: pt(LEAD), color: col('muted'),
      marginTop: pt(0), marginBottom: pt(LEAD) },
  ],
};
```

Os títulos não têm desenho: o título em ruqʿa ocupa uma caixa de linha com a altura de três linhas da grade, e o autor e a linha latina ocupam uma linha de 28 pt cada um, então a primeira linha de texto cai na grade. A linha latina leva `{dir=ltr}`, um título da esquerda para a direita num livro da direita para a esquerda ([Direção do documento, do bloco e do texto em linha](https://postext.dev/pt/docs/arabic-layout.md#direção-do-documento-do-bloco-e-do-trecho)).

## A receita completa

Um único arquivo, composto a partir da pasta da receita com o texto de exemplo e o kit comum das Receitas já incluídos; ele monta a própria página. Para executá-lo, coloque-o em um `<script type="module">` de uma página vazia ou cole-o no painel JS de um pen novo do CodePen (como módulo). Ele importa o postext do esm.sh, então não há nada para instalar nem compilar.

- Pasta da receita: https://github.com/drnachio/postext/tree/main/cookbook/vocalised-classical-prose

### script.js

```js
// ═══ Postext Cookbook · Nº 111 · Fully vocalised classical prose with Qur'anic brackets ═══
// https://postext.dev/en/cookbook/vocalised-classical-prose
// Code: MIT · Text: al-Hamadhānī, Maqāmāt, ar.wikisource (PD) · Pictures: none
// Fonts: Amiri, Aref Ruqaa (SIL OFL 1.1) · Needs postext ≥ 1.15.0
import { buildDocument, renderPageToCanvas, clearMeasurementCache } from 'https://esm.sh/postext';

const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'vocalised-classical-prose';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// Black text and a red title on a cream page, as the Beirut and Cairo editions of the
// Maqāmāt print them.
const palette = {
  ink: '#211b17', // text: a warm near-black
  rubric: '#94291d', // the accent: the title
  muted: '#6b6156', // the author's name, the Latin title line, the note on the text
  paper: '#fbf7ee', // a cream paper
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  { id: 'main-color', name: 'rubric (defaults)', value: { hex: palette.rubric, model: 'hex' } },
];
const NASKH = 'Amiri'; // the text: a Bulaq Naskh, with its own Latin for the note
const RUQAA = 'Aref Ruqaa'; // display: the title

// #region answer: a leading of 2 × the size for text with every vowel written
// Every letter of this maqāma carries its vowel: fatḥa and shadda over the letters, kasra
// and kasratān under them, often two marks on one letter. The marks sit in the leading,
// which never grows for them, so the leading is chosen for the most vocalised line of the
// book. Unvocalised prose sets well at 1.6 × the size; fully vocalised text needs
// 1.9–2.1 ×: here 14 pt on 28 pt, 2 ×, so a kasra hanging under one line clears a shadda
// standing on the line below. Any closer and the build reports the paragraph
// (arabicMarksExceedLeading) with the leading it would need.
const SIZE = 14; // pt
const LEAD = 2 * SIZE; // pt: 28
const bodyText = {
  fontFamily: NASKH, fontSize: pt(SIZE), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  // Justified with kashidas, the default in an Arabic book, written out to say so:
  // a line widens at its joins as well as at its spaces.
  textAlign: 'justify', kashida: 'auto', kashidaPatterns: 'naskh',
  firstLineIndent: em(1.5), indentAfterHeading: false, // on the start side, the right
  optimalLineBreaking: true, avoidWidows: true, avoidOrphans: true,
};
// #endregion

// #region page: a 14 × 21 cm book page, seventeen lines of 28 pt
// 166 mm of text block hold seventeen lines on the 28 pt pitch (9.88 mm). The measure is
// 106 mm, about twelve words of vocalised Naskh.
const page = {
  sizePreset: 'custom', width: mm(140), height: mm(210), dpi: 150,
  backgroundColor: col('paper'),
  // mirror: left is the inner margin, at the spine (the right edge of an odd page here)
  margins: { top: mm(22), bottom: mm(22), left: mm(18), right: mm(16), mirror: true },
};
// #endregion

// #region titles: the maqāma's title in Ruqʿa, its author and a Latin line under it
// The headings carry no design: the title's line box, three lines of the grid deep, lowers
// it on the page (a top margin is dropped at the head of a page), and the levels under it
// keep to the 28 pt pitch, so the text starts on a line of the grid.
const headings = {
  fontFamily: NASKH, color: col('ink'), textAlign: 'center',
  levels: [
    // Restated: any headings object drops the H1 break (gotcha: headings-drop-h1-break).
    { level: 1, fontFamily: RUQAA, fontWeight: 700, fontSize: pt(32), lineHeight: pt(LEAD * 3),
      color: col('rubric'), marginTop: pt(0), marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontWeight: 700, fontSize: pt(15), lineHeight: pt(LEAD), color: col('ink'),
      marginTop: pt(0), marginBottom: pt(0) },
    { level: 3, fontWeight: 400, fontSize: pt(10), lineHeight: pt(LEAD), color: col('muted'),
      marginTop: pt(0), marginBottom: pt(LEAD) },
  ],
};
// #endregion

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: 'ar', // right to left, bound on the right, digits ٠–٩ (gotcha: arabic-locale-tag)
  colorPalette,
  page,
  layout: { layoutType: 'single' }, // the default is two columns
  bodyText,
  headings,
  header: { elements: [] }, // a single maqāma: no running heads
  footer: { elements: [] },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "مقامات بديع الزمان الهمذاني"
---

# المقامة المارستانية

## بديع الزمان الهمذاني

### Al-Maqāma al-Māristāniyya · The Maqāma of the Madhouse {dir=ltr}

حَدَّثَنا عِيسَى بْنُ هِشَامٍ قَالَ: دَخَلْتُ مَارسْتانَ البَصْرَةِ وَمَعِي أَبُو داوُدَ المُتَكَلِّمُ، فَنَظَرْتُ إِلَى مَجْنُونٍ تَأْخُذُنِي عَيْنُهُ وَتَدَعُنِي فَقالَ: إِنْ تَصْدُقِ الطَّيْرُ فَأَنْتُمْ غُرَباءُ، فَقُلْنَا: كَذلِكَ، فَقَالَ: مَنِ القَوْمُ للهِ أَبُوهُمْ؟ فَقُلْتُ: أَنَا عِيسَى ابْنُ هِشامٍ وَهَذَا أَبُو دَاوُدَ المُتَكَلِّمُ فَقالَ: العَسْكَرِيُّ؟ قُلْتُ: نَعَمْ، فَقَالَ: شَاهَتِ الوُجُوهُ وَأَهْلُهَا إِنَّ الخَيْرَةَ للهِ لا لِعَبْدِهِ، وَالأُمُورَ بِيَدِ اللهِ لا بِيَدِهِ وَأَنْتُمْ يا مَجُوسَ هذِهِ الأُمَّةِ تَعِيشُونَ جَبْراً، وَتَمُوتُونَ صَبْراً وَتُسَاقُونَ إِلى المَقْدُورِ قَهْراً، وَ﴿لَوْ كُنْتُمْ في بُيُوتِكُمْ لَبَرَزَ الَّذينَ كُتِبَ عَلَيْهِمُ القَتْلُ إِلى مَضَاجِعِهِمْ﴾، أَفَلا تُنْصِفُونَ، إِنْ كَانَ الأَمْرُ كَمَا تَصِفُونَ؟ وَتَقُولونَ: خالِقُ الظُلْمِ ظَالِمٌ! أَفَلا تَقُولُونَ: خَالِقُ الهُلْكِ هَالِكٌ؟ أَتَعْلَمُونَ يَقِينَاً، أَنَّكُمْ أَخْبَثُ مِنْ إِبْليسَ دِيناً؟ قَالَ: ﴿رَبِّ بِمَا أَغْوَيْتَني﴾، فَأَقَرَّ وَأَنْكَرْتُمْ وَآمَنَ وَكَفَرْتُمْ، وَتَقُولونَ: خُيِّرَ فَاخْتَارَ، وَكَلاَّ فَإِنَّ المُخْتارَ لاَ يَبْعَجُ بَطْنَهُ، وَلاَ يَفْقَأُ عَيْنَهُ وَلا يَرْمِي مِنْ حالِقِ ابْنَهُ، فَهَلِ الإِكْرَاهُ إِلاَّ مَا تَرَاهُ؟ وَالإِكْرَاهُ مَرَّةً بِالمَرَّةِ وَمَرَّةً بِالدِّرَّةِ.

فَلْيُخْزِكُمْ أَنَّ القُرْآنَ بَغِيضُكُمْ، وَأَنَّ الحَديثَ يَغِيظُكُمْ، إِذَا سَمِعْتُمْ: ﴿مَنْ يُضْلِلِ اللهُ فَلا هَادِيَ لَهُ﴾ أَلْحَدْتُمْ وَإِذَا سَمِعْتُمْ: «زُوِيَتْ لِيَ الأَرْضُ فَأُرِيتُ مَشَارِقَهَا وَمَغَارِبَهَا» جَحَدْتُمْ وَإِذَا سَمِعْتُمْ: «عُرِضَتْ عَلَيَّ الجَنَّةُ حَتَّى هَمَمْتُ أَنْ أَقْطِفَ ثِمَارَهَا، وَعُرِضَتْ عَلَيَّ النَّارُ حَتَّى اتَّقَيْتُ حَرَّهَا بِيَدِي» أَنْغَضْتُمْ رُؤُوسَكُمْ وَلَوَيْتُمْ أَعْنَاقَكُمْ وَإِنْ قِيلَ: «عَذابُ القَبْرِ» تَطَيَّرْتُم، وَإِنْ قيلَ: «الصِّراطُ» تَغَامَزْتُمْ وَإِنْ ذُكِرَ المِيزَانُ قُلْتُمْ: مِنَ الفِرْغِ كِفَّتَاهُ، وَإِنْ ذُكِرَ الكِتابُ قُلْتُمْ: مِنَ القِدِّ دَفَّتَاهُ، يَا أَعْدَاءَ الكِتابِ وَالحَديثِ، بِماذَا تَطَّيَّرُونَ؟ أَباللهِ وَآياتِهِ وَرَسُولِهِ تَسْتَهْزِئُونَ؟ إِنَّما مَرَقتْ مَارِقَةٌ فَكَانُوا خَبَثَ الحَديثِ، ثُمَّ مَرَقْتُمْ مِنْها فَأَنْتُمْ خَبَثُ الخَبِيثِ، يَا مَخَابِيثَ الخَوارجِ، تَرَوْنَ رَأْيَهُمْ إِلاَّ القِتَالَ!

وَأَنْتَ يا ابْنَ هِشامٍ تُؤْمِنُ بِبَعْضِ الكِتَابِ وَتَكْفُرُ بِبَعْضٍ؟ سَمِعْتُ أَنَّكَ افْتَرَشْتَ مِنهُمْ شَيْطَانَةً! أَلَمْ يَنْهَكَ اللهُ عَزَّ وَجَلَّ أَنْ تَتَّخِذَ مِنْهُمْ بِطَانَةً؟ وَيْلَكَ هَلاَّ تَخَيَّرْتَ لِنُطْفَتِكَ، وَنَظَرْتَ لِعَقِبِكَ؟ ثُمَّ قَالَ: اللَّهُمَّ أَبْدِلْنِي بِهؤُلاءِ خَيْراً مِنْهُمْ، وَأَشْهِدْنِي مَلائِكَتَكَ.

قَالَ عِيسَى بْنُ هِشَامٍ: فَبَقِيتُ وَبَقِي أَبُو دَاوُدَ لا نُحِيرُ جَوَاباً، وَرَجَعْنَا عَنْهُ بِشَرٍّ وإِنِّي لأَعْرِفُ فِي أَبي دَاوُدَ انْكِسَاراً، حَتَّى إِذَا أَرَدْنَا الافْتِراقَ قَالَ: يا عِيسَى هَذا وَأَبِيكَ الحَدِيثُ، فَمَا الَّذي أَرَادَ بِالشَّيْطَانَةِ؟ قُلْتُ: لاَ واللهِ مَا أَدْرِي، غَيْرَ أَنِّي هَمَمْتُ أَنْ أَخْطُبَ إِلَى أَحَدِهِمْ وَلَمْ أُحَدِّثْ بِما هَمَمْتُ بِهِ أَحَداً، وَاللهِ لا أَفْعَلُ ذَلِكَ أَبَداً، فَقَالَ: مَا هَذا وَاللهِ إِلاَّ شَيْطَانٌ فِي أَشْطَانٍ، فَرَجَعْنَا إِلَيْهِ، وَوَقَفْنَا عَلَيْهِ، فَابْتَدَرَ بِالْمَقَالِ، وَبَدَأَنَا بِالسُّؤَالِ، فَقالَ: لَعَلَّكُمَا آثَرْتُمَا، أَنْ تَعْرِفَا مِنْ أَمْرِي ما أَنْكَرْتُمَا، فَقُلْنَا: كُنْتَ مِنْ قَبْلُ مُطَّلِعَاً على أُمُورِنَا، وَلَمْ تَعْدُ الآنَ مَا في صُدُورِنَا، فَفَسِّرْ لَنَا أَمْرَكَ، وَاكْشِفْ لَنَا سِرَّكَ، فَقالَ:

:::verse
أَنَا يَنْبُوعُ العَجَائِبْ || فِي احْتِيَالِي ذُو مَرَاتِبْ
أَنَا فِي الحَقِّ سَنَامٌ || أَنَا في البَاطِلِ غَارِبْ
أَنَا إِسكَنْدَرُ دَارِي || فِي بِلادِ اللهِ سَارِبْ
أَغْتَدِي فِي الدَّيْرِ قِسِّيساً || وَفي المَسْجِدِ رَاهِبْ
:::

:::paragraphs{dir=ltr}
**A note on the text.** Badīʿ al-Zamān al-Hamadhānī (968–1008) wrote the first maqāmāt, tales in rhymed prose in which the narrator ʿĪsā ibn Hishām meets the rogue Abū al-Fatḥ al-Iskandarī in one disguise after another. Here a madman in the asylum of Basra routs a theologian who holds that men choose their own acts. The text is that of ar.wikisource, every letter vocalised; twenty typing slips are corrected and the paragraphs are ours. The three Qurʾān quotations (3:154, 15:39, 7:186) stand between ornate brackets, the Prophet’s sayings between « ».

Set in Amiri and Aref Ruqaa (SIL OFL) · Text: al-Hamadhānī, public domain, from ar.wikisource
:::
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = { // every face the pages use, loaded before the build (gotcha: fonts-first)
  Amiri: ['400', '700'], // NASKH: the maqāma, the author, the note and its Latin
  'Aref Ruqaa': ['700'], // RUQAA: the title
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
// The arabic file of each face, which loadFonts leaves out (gotcha: arabic-fonts-subset).
await loadArabicFonts(FONTS, markdown);
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
showBook(doc, { title: t({ en: 'Fully vocalised classical prose',
  es: 'Prosa clásica con todas sus vocales' }) });

// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ─────

// ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook
function mm(value) { return { value, unit: 'mm' }; }
function pt(value) { return { value, unit: 'pt' }; }
function em(value) { return { value, unit: 'em' }; }
/** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */
function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; }
/** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */
function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; }

// ─── Kit · fonts v2 ── the same in every recipe · postext.dev/cookbook
// Postext measures with the loaded faces and caches the widths: load every face
// before the first build, from Fontsource, the files the PDF embeds too.

/** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
  const jobs = [];
  let added = 0;
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().then((ready) => { document.fonts.add(ready); added++; }, () => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return added;
}

/** Runs `build` and loads any face the pages use that FONTS missed (a regular
 *  one with a warning), then clears the measurement cache and builds again. */
async function buildWithFonts(build, text = '') {
  const tried = new Set();
  for (let round = 0; round < 3; round++) {
    kitStatus('Laying out…');
    await new Promise(requestAnimationFrame);          // let the status paint first
    const result = await Promise.resolve().then(build).catch((error) => { kitFail(error); throw error; });
    const wanted = { base: {}, variants: {} };
    for (const { font, base } of [result].flat().flatMap(fontStringsOf)) {
      const { family, weight, style } = parseFont(font);
      const key = `${family}|${weight}|${style}`;
      if (tried.has(key) || hasFace(family, weight, style)) continue;
      tried.add(key);
      (wanted[base ? 'base' : 'variants'][family] ??= []).push(`${weight}${style === 'italic' ? 'i' : ''}`);
    }
    if (Object.keys(wanted.base).length) {
      console.warn(`[cookbook] FONTS does not list ${JSON.stringify(wanted.base)}: loading them.`);
    }
    const added = await loadFonts(wanted.base, text) + await loadFonts(wanted.variants, text, { optional: true });
    if (added === 0) return result;
    clearMeasurementCache();
  }
  throw new Error('The fonts did not settle after three builds.');
}

/** Every font string of the layout; `base` marks a block's own face. */
function fontStringsOf(doc) {
  const found = new Map();
  const walk = (node) => {
    if (!node || typeof node !== 'object') return;
    if (Array.isArray(node)) { node.forEach(walk); return; }
    for (const [key, value] of Object.entries(node)) {
      if (typeof value === 'string' && /fontString$/i.test(key)) {
        found.set(value, found.get(value) || key === 'fontString');
      } else if (value && typeof value === 'object') walk(value);
    }
  };
  walk(doc.pages);
  walk(doc.blocks);
  return [...found].map(([font, base]) => ({ font, base }));
}

/** '700 37.5px Open Sans' / 'italic 400 13px "Source Serif 4"' → { family, weight, style }.
 *  A string with no weight ('95.8px Young Serif', from a design text) is 400. */
function parseFont(font) {
  const m = /^(?:(italic|oblique)\s+)?(?:small-caps\s+)?(?:(\d+|bold|normal)\s+)?[\d.]+px\s+(.+)$/.exec(font.trim());
  if (!m) throw new Error(`Unexpected font string: ${font}`);
  const weight = m[2] === 'bold' ? 700 : !m[2] || m[2] === 'normal' ? 400 : Number(m[2]);
  return { family: m[3].replace(/^["']|["']$/g, ''), weight, style: m[1] ? 'italic' : 'normal' };
}

/** A loaded FontFace covers this family, weight and style (fonts.check() would
 *  also say yes for families nobody declared). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

/** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */
function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); }

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

// ─── Kit · viewer v1 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. */
function showPages(docs, { title, width = 460 } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · arabic v1 ── Arabic-script faces · postext.dev/cookbook
// Fontsource ships an Arabic family as one file per subset and weight: the
// `arabic` file holds the letters, the harakat, the Arabic-Indic digits, the
// Arabic punctuation and the presentation forms; `latin` and `latin-ext`
// hold the rest. loadFonts loads the latin files; this block adds the arabic
// file of every Arabic family, for the canvas and for the PDF, which shapes
// the letters with HarfBuzz from the same bytes.

/** The code points of Fontsource's `arabic` subset, as its stylesheets
 *  declare them (the same unicode-range the browser picks the file by). A
 *  function, not a const: the kit is inlined after the recipe's top-level
 *  awaits, and a const read before its line throws, where a function
 *  declaration is hoisted. */
function arabicRange() {
  return 'U+0600-06FF,U+0750-077F,U+0870-088E,U+0890-0891,U+0897-08E1,U+08E3-08FF,'
    + 'U+200C-200E,U+2010-2011,U+204F,U+2E41,U+FB50-FDFF,U+FE70-FE74,U+FE76-FEFC,U+102E0-102FB,'
    + 'U+10E60-10E7E,U+10EC2-10EC4,U+10EFC-10EFF,U+1EE00-1EEFF';
}

/** Whether code point `cp` is in the arabic file. */
function inArabicRange(cp) {
  inArabicRange.ranges ??= arabicRange().split(',').map((part) => {
    const [lo, hi = lo] = part.slice(2).split('-');
    return [parseInt(lo, 16), parseInt(hi, 16)];
  });
  return inArabicRange.ranges.some(([lo, hi]) => cp >= lo && cp <= hi);
}

/** Whether Fontsource serves `family` with an `arabic` subset. Fails when
 *  the API does not answer: an Arabic face taken for a Latin one would set
 *  its letters in a system face. */
async function isArabicFamily(family) {
  const meta = await fontsourceMeta(family);
  if (!meta) throw new Error(`api.fontsource.org did not describe ${family}: reload to try again`);
  return !!meta.subsets?.includes('arabic');
}

/** The arabic file of a face. */
function arabicFileUrl(family, weight, style) {
  const id = fontsourceId(family);
  return `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-arabic-${weight}-${style}.woff2`;
}

/** faces = { Amiri: ['400', '700'] }, as for loadFonts, after it: the whole
 *  FONTS object may be passed, its families without an arabic subset are
 *  left alone. Adds the arabic file of every listed weight of each Arabic
 *  family (the latin files come from loadFonts) and loads it. `text` is
 *  the sample: fails when it holds an Arabic-script character the arabic
 *  file does not cover. List every weight the pages set in Arabic: a weight
 *  left to buildWithFonts gets the latin file only, and its Arabic letters
 *  fall back to a system face. Resolves to the number of files loaded. */
async function loadArabicFonts(faces, text = '') {
  kitStatus('Loading fonts…');
  let loaded = 0;
  try {
    const outside = [...new Set(text)].filter((ch) => /\p{Script=Arabic}/u.test(ch) && !inArabicRange(ch.codePointAt(0)));
    if (outside.length) throw new Error(`Fontsource's arabic files have no ${outside.slice(0, 12).join(' ')}`);
    for (const [family, specs] of Object.entries(faces)) {
      if (!(await isArabicFamily(family))) continue;
      for (const spec of new Set(specs)) {
        const weight = parseInt(spec, 10);
        const style = spec.endsWith('i') ? 'italic' : 'normal';
        const face = new FontFace(family, `url(${arabicFileUrl(family, weight, style)}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: arabicRange() });
        document.fonts.add(await face.load().catch(() => {
          throw new Error(`Fontsource has no arabic file for ${family} ${weight} ${style}`);
        }));
        loaded++;
      }
    }
  } catch (error) {
    kitFail(error);
    throw error;
  }
  return loaded;
}

/** The PDF font provider for recipes with Arabic faces: a family with an
 *  arabic subset gets its arabic file when its pages set Arabic letters
 *  (`request.codePoints`), then its latin file, and its latin-ext file for
 *  the letters beyond latin (transliteration: ā ḥ ʿ). The arabic file comes
 *  first: it also holds the space and the brackets, so a line of Arabic is
 *  shaped as one run and not cut at every space. Any other family goes to
 *  fontsourceProvider (the "pdf" block). */
async function arabicPdfProvider(family, weight, style, request) {
  if (!(await isArabicFamily(family))) return fontsourceProvider(family, weight, style, request);
  const meta = await fontsourceMeta(family);
  const weights = meta.weights?.length ? meta.weights : [400, 700];
  const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a));
  const s = style === 'italic' && !meta.styles.includes('italic') ? 'normal' : style;
  const wanted = [...(request?.codePoints ?? [])];
  const id = fontsourceId(family);
  const urls = [];
  if (!wanted.length || wanted.some(inArabicRange)) urls.push(arabicFileUrl(family, w, s));
  urls.push(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-latin-${w}-${s}.woff2`);
  if (meta.subsets.includes('latin-ext') && wanted.some((cp) => /[Ā-˿Ḁ-ỿ]/u.test(String.fromCodePoint(cp)))) {
    urls.push(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-latin-ext-${w}-${s}.woff2`);
  }
  return Promise.all(urls.map(async (url) => {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`Fontsource file ${url} (${res.status})`);
    return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
  }));
}

// ─── Kit · book v1 ── books bound on either edge · postext.dev/cookbook
// A book bound on the right (Arabic, Hebrew or Persian text, vertical
// Chinese, or page.binding 'right') opens from what a Latin reader calls
// the back: page 1 lies alone on the left of the spine, then [3 | 2].

/** showPages for a book bound on either edge. A right-bound book (the
 *  document says so: doc.binding is 'right' for page.binding 'right', for
 *  text that runs right to left and for vertical text, when the binding is
 *  left to 'auto') lies on the desk as it opens: page 1 alone on the left
 *  of the spine, then [3 | 2], the spine shade on each page's inner edge.
 *  `binding` ('left' | 'right') overrides the document's. */
function showBook(docs, { binding, ...options } = {}) {
  const count = showPages(docs, options);
  const right = (binding ?? [docs].flat()[0]?.binding) === 'right';
  if (!document.getElementById('pt-kit-book')) {
    // The pages keep direction ltr, as in a left-bound book: a canvas takes
    // the direction its element inherits, and under the spread's rtl a run
    // painted for an ltr canvas would end where the engine starts it.
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit-book">
      .pt-spread[dir="rtl"] canvas { direction: ltr; }
      .pt-spread[dir="rtl"] figure:first-child canvas { box-shadow: inset 14px 0 14px -14px rgb(0 0 0 / .18),
        0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
    </style>`);
  }
  // Each pair stays [verso, recto] in the page; right to left, the verso
  // sits on the right. Phones stack the pages in reading order either way.
  for (const spread of document.querySelectorAll('#pages > .pt-spread')) spread.dir = right ? 'rtl' : 'ltr';
  document.getElementById('pages').dataset.binding = right ? 'right' : 'left';
  return count;
}

// ─── /Kit ───────────────────────────────────────────────────────────────────────
```

## Variações

### Tirar as vogais para uma edição de leitura

`tashkil: 'strip'` remove da composição as vogais breves e a shadda, de modo que o mesmo texto pode ser composto com 1,6 vez o corpo; `'strip-vowels'` mantém a shadda.

```diff
-  textAlign: 'justify', kashida: 'auto', kashidaPatterns: 'naskh',
+  textAlign: 'justify', kashida: 'auto', kashidaPatterns: 'naskh', tashkil: 'strip',
```

### Ver o aviso

Com a entrelinha em 1,6 vez o corpo, a composição aponta seis parágrafos com `arabicMarksExceedLeading`, cada um com a entrelinha de que precisa, até 1,89 em neste texto.

```diff
-const LEAD = 2 * SIZE; // pt: 28
+const LEAD = 1.6 * SIZE; // pt: 22.4
```

## Erros comuns

- **Marque um livro árabe como 'ar', não com LANG.** As edições de uma receita são en e es, mas uma amostra árabe é árabe nas duas: `locale: LANG` a comporia da esquerda para a direita, a encadernaria pela esquerda, numeraria as suas páginas 1 2 3 e chamaria as suas figuras de Figure ou Figura. Escreva você mesmo a marcação: 'ar' (algarismos arábico-índicos, a convenção do Machrek), uma região como 'ar-EG' ou 'ar-SA', ou 'ar-MA', 'ar-DZ' ou 'ar-TN' para uma edição magrebina com algarismos europeus. Uma página latina que só cita árabe mantém o seu próprio locale.
- **Fontes árabes precisam do seu arquivo arabic, pelo bloco arabic.** O Fontsource serve Amiri, Noto Naskh Arabic ou Scheherazade New em um arquivo por subconjunto, e as letras árabes estão no arquivo arabic. loadFonts baixa só latin (e latin-ext), então na tela o árabe vem de uma fonte do sistema e é medido errado, e o fontsourceProvider entrega ao PDF esse arquivo latin, que imprime caixas vazias. Inclua o bloco arabic do kit, chame loadArabicFonts(FONTS, markdown) depois de loadFonts, com todos os pesos que as páginas usam em árabe listados em FONTS, e passe a renderToPdf fontProvider: arabicPdfProvider.
- **Qualquer objeto headings desativa a quebra de página do H1.** Por padrão, um H1 salta para uma página ímpar (always-odd), mas passar qualquer objeto headings redefine esse padrão, então os capítulos ficam emendados e span: 'page' não faz nada. Declare de novo headings.levels[0].breakBefore: { enabled: true, parity } em toda configuração.
- **Carregue todas as fontes antes do layout.** O motor de layout mede o texto com as fontes que o navegador carregou e guarda as larguras em cache, então uma fonte que chega depois da primeira composição deixa quebras de linha erradas e um PDF que não corresponde mais à tela. Carregue antes todos os pesos e estilos e chame clearMeasurementCache() antes de recompor quando alguma chegar atrasada.
- **Aviso de diagramação: Sinais vocálicos encostam na linha vizinha** (`arabicMarksExceedLeading`). Um parágrafo de árabe vocalizado coloca os sinais vocálicos (fatḥa, kasra, shadda, tanwīn…) na entrelinha, que não cresce por causa deles: onde um sinal sobre uma palavra de uma linha alcança as letras ou os sinais pendurados sob a palavra de cima, as duas linhas se tocam. Solução: Aumente `bodyText.lineHeight` (1,7–1,85 em para texto vocalizado em parte, 1,9–2,1 em para verso totalmente vocalizado), dê ao parágrafo um estilo de parágrafo com mais entrelinha ou defina `bodyText.tashkil: 'strip-vowels'` para uma edição sem vogais. ([Documentação](https://postext.dev/pt/docs/configuration.md#texto-árabe))

## Créditos

- Receita: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Texto: Al-Maqāma al-Māristāniyya, fully vocalised, as ar.wikisource gives it (revision 601165), with twenty typing slips corrected, its paragraphs divided and its Qurʾān quotations set between ﴿ ﴾: Badīʿ al-Zamān al-Hamadhānī ([fonte](https://ar.wikisource.org/wiki/مقامات_بديع_الزمان_الهمذاني/المقامة_المارستانية)), domínio público
- Texto: The note on the text, the Latin title line and the colophon: Postext Cookbook, original
- Tipos: Amiri (OFL-1.1), Aref Ruqaa (OFL-1.1)
- Código: MIT · Conteúdo de exemplo: MIT

## Relacionadas

- [Nº 105 · Romance árabe da direita para a esquerda, lombada à direita](https://postext.dev/pt/cookbook/arabic-novel-right-bound.md): A abertura de um romance árabe numa página de 14 × 21 cm: locale 'ar' compõe o texto da direita para a esquerda, encaderna à direita e imprime ١٢ nos fólios. · Nível 2 (Intermediário) · Ficção, teatro e prosa literária
- [Nº 130 · Japonês em letras latinas: o diário em rōmaji de Takuboku](https://postext.dev/pt/cookbook/romaji-nikki.md): Um texto em rōmaji marcado ja-Latn: regras japonesas para os kana, nenhuma hifenização no latim e os mácrons de uma fonte latina levados ao PDF. · Nível 2 (Intermediário) · Ficção, teatro e prosa literária
- [Nº 074 · Uma página de romance chinês numa grade de 28 × 28](https://postext.dev/pt/cookbook/chinese-novel-horizontal.md): “Minha terra natal”, de Lu Xun, num 大32开: 28 caracteres por linha, 28 linhas por página, quebras GB, pontuação Kaiming e espaço entre han e latim. · Nível 2 (Intermediário) · Ficção, teatro e prosa literária
