
Hebrew Document Generator
- 74 installs
- 21 repo stars
- Updated August 3, 2026
- skills-il/localization
Generate Hebrew-language documents with correct RTL formatting.
About
A localization skill that generates Hebrew-language documents with proper right-to-left layout. Builders use it to produce correctly formatted Hebrew docs and reports.
- Hebrew documents
- RTL formatting
- Doc generation
Hebrew Document Generator by the numbers
- 74 all-time installs (skills.sh)
- Ranked #346 of 688 Office & Documents skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/skills-il/localization --skill hebrew-document-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 74 |
|---|---|
| repo stars | ★ 21 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | skills-il/localization ↗ |
What it does
Generate Hebrew-language documents with correct RTL formatting.
Files
Hebrew Document Generator
Instructions
Step 1: Choose the Output Format
| Format | Library | Best For | RTL Support |
|---|---|---|---|
| reportlab | Invoices, tax docs, printable forms | Register Hebrew font, use canvas.drawRightString() | |
| WeasyPrint | Styled documents from HTML/CSS | Native via dir="rtl" in HTML | |
| DOCX | python-docx | Contracts, proposals, meeting minutes | Set paragraph bidi; split mixed runs, set w:cs font + w:rtl on Hebrew runs only |
| PPTX | pptxgenjs (Node) | Presentations, slide decks | RTL text boxes with rtlMode: true |
Step 2: Install Dependencies and Hebrew Fonts
Python PDF generation:
pip install reportlab weasyprintPython DOCX generation:
pip install python-docx python-bidiNode.js PPTX generation:
npm install pptxgenjsRecommended Hebrew fonts (install on system):
| Font | Style | Best For | Source |
|---|---|---|---|
| Heebo | Sans-serif, modern | Web-style documents, invoices | Google Fonts |
| David | Classic serif | Legal contracts, formal letters | System (Windows/macOS) |
| Narkisim | Serif, elegant | Proposals, invitations | System (Windows) |
| Frank Ruehl | Traditional serif | Academic, literary | Google Fonts (Frank Ruhl Libre) |
| Rubik | Sans-serif, rounded | Presentations, marketing | Google Fonts |
| Assistant | Sans-serif, clean | Business correspondence | Google Fonts |
See references/hebrew-fonts.md for download links and installation instructions.
Step 3: Generate Hebrew PDF with reportlab
See scripts/generate_doc.py for the full generation pipeline.
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import mm
from bidi import get_display # python-bidi 0.6.x; see note below
# Register Hebrew font
pdfmetrics.registerFont(TTFont('Heebo', 'Heebo-Regular.ttf'))
pdfmetrics.registerFont(TTFont('Heebo-Bold', 'Heebo-Bold.ttf'))
def create_hebrew_pdf(filename, title, content_lines):
c = canvas.Canvas(filename, pagesize=A4)
width, height = A4
# Title -- right-aligned for RTL
c.setFont('Heebo-Bold', 18)
hebrew_title = get_display(title)
c.drawRightString(width - 20*mm, height - 30*mm, hebrew_title)
# Body lines
c.setFont('Heebo', 12)
y = height - 50*mm
for line in content_lines:
display_line = get_display(line)
c.drawRightString(width - 20*mm, y, display_line)
y -= 7*mm
c.save()Key points for reportlab Hebrew:
- Always use
get_display()from python-bidi to reorder characters - Use
drawRightString()for right-aligned RTL text - Register TTF Hebrew fonts explicitly -- reportlab has no built-in Hebrew support
- Set line height to at least 1.5x font size for Hebrew readability
- python-bidi import: the canonical, recommended import is
from bidi import get_display(top-level). The olderfrom bidi.algorithm import get_displaypath still imports in current 0.6.x as a back-compat parallel module, but prefer the top-level one. python-bidi 0.6.x also dropped support for Python below 3.9. - Multi-line text:
drawRightString()draws a single line and does NOT wrap. For any body text longer than one line, use reportlab'sParagraphflowable (fromreportlab.platypus) with a right-aligned, RTLParagraphStyleinstead. The bundledscripts/generate_doc.pyuses per-linedrawRightStringfor compact fixed-layout documents (invoices, receipts); it will clip long Hebrew strings. Reach forParagraph/ platypus flowables for contracts or any wrapping body copy.
Mixed Hebrew / Latin / Digit Lines
The single most common RTL failure in generated documents is a line that mixes a Hebrew description with LTR numbers and a currency symbol, for example an invoice line item. get_display() handles the bidi reordering, but you must pass the whole logical string in one call so the algorithm sees the full context:
from bidi import get_display
# Logical order: Hebrew description, then qty, unit price, currency
line = 'ייעוץ טכני (3 שעות) - 1,500.00 ש"ח'
c.setFont('Heebo', 11)
c.drawRightString(width - 20 * mm, y, get_display(line))The digits, the comma, the period, and the parentheses all stay in their correct LTR positions because the bidi algorithm resolves them relative to the surrounding Hebrew. Do NOT split the line into pieces and reorder them yourself, and do NOT call get_display() on the Hebrew part only, both approaches break the number ordering.
Step 4: Generate Hebrew PDF with WeasyPrint
from weasyprint import HTML
html_content = """
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="utf-8">
<style>
@font-face {
font-family: 'Heebo';
src: url('Heebo-Regular.ttf');
}
body {
font-family: 'Heebo', sans-serif;
direction: rtl;
font-size: 12pt;
line-height: 1.7;
}
h1 { font-size: 18pt; text-align: start; }
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #333;
padding: 6px 10px;
text-align: start;
}
</style>
</head>
<body>
<h1>חשבונית מס</h1>
<!-- Document content here -->
</body>
</html>
"""
HTML(string=html_content).write_pdf('invoice.pdf')WeasyPrint advantages for Hebrew:
- Full CSS support including logical properties
- Native RTL via HTML
dirattribute - Tables render correctly in RTL
- Supports
@font-facefor custom Hebrew fonts
Step 5: Generate Hebrew DOCX with python-docx
DOCX is where mixed Hebrew/English breaks most often, and Microsoft Word's bidi engine is stricter than the Unicode standard. LibreOffice, macOS Preview/Quick Look, and most viewers render forgiving output that HIDES Word-only bugs, so always verify in Word itself, not a substitute renderer. Four rules, each learned against real Word:
1. Every Hebrew paragraph carries <w:bidi/> (RTL base direction); a pure-English line (a lab value, a drug name, an English-only row) gets LTR base + left alignment instead. The helper picks this per paragraph from whether the line contains any Hebrew, so English-only rows do not hang off the right margin in an otherwise Hebrew document. 2. Do NOT put `<w:rtl/>` on the runs of a MIXED Hebrew+English paragraph. This is the single biggest Word trap. Word honors <w:rtl/> strictly: any Latin or number that lands in (or beside) an rtl-flagged run gets force-reversed, so 7/2023 prints as 2023/7, an embedded KI-67 code flips, and the parentheses around a mixed group like (גסטרית, KI-67) mis-pair. In a mixed paragraph the paragraph's own <w:bidi/> already orders the line correctly, leave every run unflagged. 3. Flag `<w:rtl/>` ONLY on Hebrew runs of a paragraph that has no Latin letters (a pure-Hebrew label or heading, digits allowed). There the flag is what anchors a trailing colon (מחלות רקע:) to the left end. A leading section-number marker (2., 10.) is additionally merged into the Hebrew run (_merge_list_marker) so its period does not flip to .2; a date like 13/01/2026 is left as its own LTR run so Word does not reverse it. The split stays by script so each run still gets the right complex-script font. 4. Every run sets the complex-script font (w:cs) and size (w:szCs). Hebrew is a "complex script" in Word's model, so w:ascii/w:sz alone never govern the Hebrew glyphs. Omitting w:cs/w:szCs is the most common cause of "the font/size I set did nothing and the Hebrew looks broken". Bold and italic are the same: w:b/w:i only affect Latin, you also need w:bCs/w:iCs. Never insert Unicode directional isolates (U+2066-2069) or marks to force order, Word renders them as visible `.notdef` boxes in the David font even though other viewers hide them.
import re
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
# Hebrew block + Hebrew presentation forms. Used to pick each run's direction.
_HEB = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]') # Hebrew block + presentation forms
_LIST_MARKER = re.compile(r'^\d{1,2}\.$') # 1-2 digit list marker, e.g. "2."
def _strong(ch):
"""True for a Hebrew letter, False for a strong-LTR char (Latin OR ASCII
digit), None for neutral. Digits count as LTR so a number never rides inside
a Hebrew run (Word force-reverses numbers caught in an rtl-flagged run)."""
if _HEB.match(ch):
return True
if ch.isascii() and ch.isalnum():
return False
return None
def _split_by_script(text):
"""Split a mixed string into (segment, is_rtl) runs.
Each run's direction is set by its STRONG characters; neutral chars
(spaces, digits, punctuation) attach to the current run. Leading neutrals
inherit the direction of the first strong character in the WHOLE string
(falling back to RTL for an all-neutral string in a Hebrew document), so a
Latin-dominant line that starts with a digit or bracket is not mis-flagged
RTL. The split groups characters so each run can carry the correct
complex-script font; in Word, run DIRECTION is governed by the rtl rules
in add_rtl_paragraph, not by this split alone.
"""
default_rtl = next((s for s in (_strong(c) for c in text) if s is not None), True)
segments, buf, buf_rtl = [], '', None
for ch in text:
s = _strong(ch)
kind = s if s is not None else (buf_rtl if buf_rtl is not None else default_rtl)
if buf_rtl is None or kind == buf_rtl:
buf, buf_rtl = buf + ch, kind
else:
segments.append((buf, buf_rtl))
buf, buf_rtl = ch, kind
if buf:
segments.append((buf, buf_rtl))
return segments
def _shift_boundary_spaces(segments):
"""Move a space at the END of an LTR run that directly precedes an RTL run to
the START of that RTL run. Word trims a run's trailing whitespace at a
direction boundary, which glues a leading number to its heading
("2.\u05db\u05d5\u05ea\u05e8\u05ea"); a leading space on the RTL run survives and restores
the gap. Without this, numbered Hebrew headings lose the space after "N.".
"""
out = [[seg, rtl] for seg, rtl in segments]
for i in range(len(out) - 1):
seg, rtl = out[i]
nseg, nrtl = out[i + 1]
if rtl is False and nrtl is True and seg.endswith(' '):
stripped = seg.rstrip(' ')
out[i][0] = stripped
out[i + 1][0] = seg[len(stripped):] + nseg
return [(s, r) for s, r in out if s]
def _merge_list_marker(segments):
"""A leading short list-number marker ("2.", "10.") on an RTL line must be part
of the Hebrew RTL run, or Word floats its period to the wrong side (".2").
Merge a leading LTR marker run into the Hebrew run that follows it. Matches
only 1-2 digits + period, never a date like 13/01/2026 (which must stay an
LTR run so Word does not reverse it)."""
if (len(segments) >= 2 and segments[0][1] is False
and _LIST_MARKER.match(segments[0][0].strip())
and segments[1][1] is True):
return [(segments[0][0] + segments[1][0], True)] + list(segments[2:])
return list(segments)
def _para_is_rtl(text):
"""Choose the paragraph base direction for a Hebrew document.
Any Hebrew letter -> RTL base: a Hebrew sentence routinely embeds English
terms, drug names, or numbers and must still flow right-to-left. No Hebrew
but Latin present -> LTR base, so a pure-English line (a lab value, an
English-only clinical row) renders left-aligned instead of hugging the right
margin. All-neutral (digits/punctuation only) -> RTL, the document default.
This is the fix for "English-only lines come out right-aligned and the
document still looks RTL-broken".
"""
if _HEB.search(text):
return True
if any(ch.isascii() and ch.isalnum() for ch in text):
return False
return True
def add_rtl_paragraph(doc, text, font='David', size=12, bold=False, italic=False,
heading_level=None):
"""Add a paragraph that renders mixed Hebrew/Latin/digit text correctly,
auto-selecting RTL or LTR base direction from whether the line has Hebrew.
Covers BODY paragraphs only. Table cells, headers/footers, and numbered
lists are separate document stories: apply the same logic to each of their
paragraphs, and add `<w:bidi/>` to the section `sectPr` for a fully RTL page.
"""
p = doc.add_heading(level=heading_level) if heading_level else doc.add_paragraph()
# (1) paragraph base direction: RTL when the line contains any Hebrew (a
# Hebrew sentence routinely embeds English terms and must still flow
# right-to-left); LTR for a pure-Latin line so an English-only row reads
# left-aligned instead of hugging the right margin.
base_rtl = _para_is_rtl(text)
pPr = p._p.get_or_add_pPr()
if base_rtl:
pPr.append(pPr.makeelement(qn('w:bidi'), {}))
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT if base_rtl else WD_ALIGN_PARAGRAPH.LEFT
# A paragraph with ANY Latin letter is "mixed": never rtl-flag its runs
# (rule 2 above). A paragraph with only Hebrew (+digits/punct) is "pure":
# its Hebrew runs DO get rtl, to anchor trailing colons and leading numbers.
para_has_latin = any(ch.isascii() and ch.isalpha() for ch in text)
for segment, is_rtl in _shift_boundary_spaces(_merge_list_marker(_split_by_script(text))):
run = p.add_run(segment)
rPr = run._r.get_or_add_rPr()
# rPr children must stay in OOXML schema order: rFonts, b, bCs, i, iCs, sz, szCs, rtl
rPr.append(rPr.makeelement(qn('w:rFonts'), {
qn('w:ascii'): font, qn('w:hAnsi'): font, qn('w:cs'): font}))
if bold:
# bold needs BOTH w:b (Latin) and w:bCs (complex script / Hebrew)
rPr.append(rPr.makeelement(qn('w:b'), {}))
rPr.append(rPr.makeelement(qn('w:bCs'), {}))
if italic:
# italic likewise needs BOTH w:i and w:iCs for Hebrew
rPr.append(rPr.makeelement(qn('w:i'), {}))
rPr.append(rPr.makeelement(qn('w:iCs'), {}))
# (3) complex-script font size, so the size applies to Hebrew
rPr.append(rPr.makeelement(qn('w:sz'), {qn('w:val'): str(size * 2)}))
rPr.append(rPr.makeelement(qn('w:szCs'), {qn('w:val'): str(size * 2)}))
# (2) Flag rtl ONLY on Hebrew runs of a paragraph with no Latin letters.
# In a mixed Hebrew+English paragraph, NO run is flagged, or Word
# force-reverses the embedded numbers/Latin and mis-pairs parens.
# The paragraph <w:bidi/> alone orders mixed lines correctly.
if is_rtl and not para_has_latin:
rPr.append(rPr.makeelement(qn('w:rtl'), {}))
return p
doc = Document()
doc.styles['Normal'].font.name = 'David'
doc.styles['Normal'].font.size = Pt(12)
add_rtl_paragraph(doc, 'חוזה שירותים', size=18, bold=True, heading_level=1)
add_rtl_paragraph(doc, 'ההסכם נחתם בין חברת Acme בע"מ לבין הלקוח (גרסה 2).')
doc.save('contract.docx')Do NOT call `get_display()` on DOCX text. Unlike reportlab (which draws pre-positioned glyphs and therefore needs python-bidi to reorder them), Word applies the bidi algorithm itself. Pre-shaping a string with get_display() and then handing it to python-docx double-applies the algorithm and scrambles the result. get_display() belongs to the PDF path only.
This helper covers body paragraphs. Tables, headers/footers, and numbered/bulleted lists are separate document stories the helper does not reach: apply the same <w:bidi/> + per-script-run logic to each of their paragraphs, and add <w:bidi/> to the section sectPr for full RTL page flow.
Step 6: Generate Hebrew PPTX with pptxgenjs
const pptxgen = require('pptxgenjs');
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9';
pptx.rtlMode = true;
const slide = pptx.addSlide();
// Hebrew title
slide.addText('סקירה רבעונית', {
x: 0.5, y: 0.5, w: '90%', h: 1.0,
fontSize: 28,
fontFace: 'Heebo',
color: '1a1a2e',
align: 'right',
rtlMode: true,
bold: true,
});
// Hebrew bullet points
slide.addText([
{ text: 'תוצאות כספיות', options: { bullet: true, rtlMode: true } },
{ text: 'יעדים לרבעון הבא', options: { bullet: true, rtlMode: true } },
{ text: 'סיכום פעילות', options: { bullet: true, rtlMode: true } },
], {
x: 0.5, y: 2.0, w: '90%', h: 3.0,
fontSize: 18,
fontFace: 'Heebo',
align: 'right',
rtlMode: true,
});
pptx.writeFile({ fileName: 'quarterly-review.pptx' });Step 7: Israeli Business Document Templates
See references/templates.md for complete field specifications per document type.
| Template | Hebrew Name | Required Fields |
|---|---|---|
| Tax Invoice | חשבונית מס | Business name, Osek Murshe number, date, line items, VAT (18%), total |
| Contract | חוזה | Parties, TZ/company numbers, terms, signatures, date |
| Price Proposal | הצעת מחיר | Business details, itemized pricing, validity period, terms |
| Meeting Minutes | פרוטוקול | Date, attendees, agenda, decisions, action items |
| Receipt | קבלה | Business name, receipt number, amount, payment method, date |
Tax Invoice (Heshbonit Mas) required fields by Israeli law:
- Business name and address
- Osek Murshe (authorized dealer) number
- Sequential invoice number
- Date of issue
- Customer name and TZ/company number
- Line items with description, quantity, unit price
- Subtotal, VAT at 18%, and total in NIS
- Allocation number (Mispar Haktzaa / מספר הקצאה) under the Israel Invoices model for a tax invoice at or above the current threshold. The threshold is being phased down (20,000 NIS in 2025, 10,000 NIS from Jan 2026, 5,000 NIS from 1 June 2026, pre-VAT). At/above the threshold the BUYER cannot deduct the input VAT unless the seller obtained a Tax Authority allocation number and printed it on the invoice. Add an allocation-number field to any invoice template and treat the threshold as time-sensitive (verify the current figure against Rashut HaMisim).
Examples
Example 1: Generate Tax Invoice PDF
User says: "Create a Hebrew tax invoice PDF for my business" Result: Use reportlab or WeasyPrint to generate A4 PDF with RTL layout, business header, sequential invoice number, itemized table, VAT calculation at 18%, totals in NIS with shekel symbol, and Hebrew font throughout.
Example 2: Create Hebrew Contract DOCX
User says: "Draft a Hebrew service contract as a Word document" Result: Use python-docx with the add_rtl_paragraph helper (Step 5): <w:bidi/> paragraphs, per-script run splitting so embedded English/numbers stay in place, w:cs font + w:szCs size, David font, RTL alignment, structured sections (parties, scope, payment terms, termination, signatures), proper Hebrew legal phrasing.
Example 3: Build Hebrew Presentation
User says: "Make a Hebrew PowerPoint for our quarterly review" Result: Use pptxgenjs with rtlMode enabled, Heebo font, right-aligned text boxes, RTL bullet points, Hebrew slide titles, and professional layout.
Example 4: Batch Document Generation
User says: "Generate 50 Hebrew invoices from a CSV file" Result: Read CSV data, iterate rows, use scripts/generate_doc.py to produce individual PDFs with unique invoice numbers, customer details, and line items per row.
Bundled Resources
Scripts
scripts/generate_doc.py- Generate Hebrew PDF documents with reportlab: register Hebrew fonts, apply RTL text reordering with python-bidi, produce Israeli business documents (invoices, receipts) with proper VAT calculations and NIS formatting. Run:python scripts/generate_doc.py --help
References
references/hebrew-fonts.md- Hebrew font catalog with recommended fonts for different document types (sans-serif, serif, monospace), Google Fonts download links, system font availability matrix, font pairing suggestions, and installation instructions for macOS, Linux, and Windows.references/templates.md- Israeli business document templates with required fields per document type (tax invoice, contract, proposal, receipt, meeting minutes), Israeli legal requirements for invoices, VAT rules, and standard Hebrew business phrasing.
Reference Links
| Source | URL | What to Check |
|---|---|---|
| reportlab documentation | https://docs.reportlab.com/ | Canvas API, platypus flowables, font registration |
| WeasyPrint documentation | https://doc.courtbouillon.org/weasyprint/stable/ | HTML/CSS to PDF, RTL support, @font-face |
| python-docx documentation | https://python-docx.readthedocs.io/ | Document model, runs, paragraph properties |
| python-bidi (PyPI) | https://pypi.org/project/python-bidi/ | Current version, import path, changelog |
| Israeli tax invoice requirements | https://he.wikipedia.org/wiki/חשבונית_מס | Mandatory fields for a Heshbonit Mas; cross-check against current Israel Tax Authority rules |
For binding legal requirements always confirm against the current Israel Tax Authority (Rashut HaMisim) guidance, the Wikipedia entry is a starting orientation, not the authority.
Recommended MCP Servers
No MCP server applies to this skill. Hebrew document generation runs entirely through local Python and Node libraries (reportlab, WeasyPrint, python-docx, pptxgenjs); there is no external service to wrap as an MCP server. Use the bundled scripts and the code in the Instructions section directly.
Gotchas
get_display()must be applied per line at draw time, immediately beforedrawRightString(), NOT once on a whole multi-line document or block. The bidi algorithm is not idempotent: running it on text that was already reordered double-reverses the characters and produces scrambled output. A common agent mistake is to "pre-process" a whole list of lines throughget_display()and then call it again inside the draw loop.- PDF generators often default to left-to-right text flow. Hebrew documents MUST use RTL paragraph direction, and mixed Hebrew-English text requires proper BiDi (bidirectional) algorithm support.
- DOCX (python-docx) has the opposite trap from PDF: do NOT run
get_display()on the text, Word applies the bidi algorithm itself and pre-shaping double-reverses it. The two failure modes that produce "broken" Hebrew Word files are (a) putting a whole mixed Hebrew/English line in ONE run flagged<w:rtl/>(the English jumps sides and punctuation reflows) and (b) setting onlyw:ascii/w:szand never the complex-scriptw:csfont /w:szCssize (your font and size silently never apply to the Hebrew). Split mixed lines per script, flag only Hebrew runs rtl, and setw:cs+w:szCson every run. - A run with no explicit direction inherits the paragraph base direction. After
add_rtl_paragraphadds a Hebrew paragraph, appending another run later (e.g. a signature line) without re-running the per-script split can leave that run unmarked, set its direction explicitly rather than assuming it inherits correctly. - Agents may pick fonts that lack Hebrew character support (e.g., Arial works, but many decorative Latin fonts do not). Always verify the font includes the Hebrew Unicode range (U+0590-U+05FF).
- Hebrew date formatting uses DD/MM/YYYY in secular context and Hebrew calendar dates (e.g., 15 Adar 5786) for religious/traditional documents. Agents may default to MM/DD/YYYY.
- Legal documents in Israel require specific formatting: nikud (vowel marks) is NOT used in standard business/legal Hebrew. Agents may add nikud thinking it improves clarity, but it actually looks unprofessional in formal documents.
Troubleshooting
Error: "Hebrew characters display as boxes or question marks"
Cause: Hebrew font not registered or not found on system Solution: Download a Hebrew TTF font (e.g., Heebo from Google Fonts), register it with pdfmetrics.registerFont() for reportlab, or install it as a system font for WeasyPrint.
Error: "Text appears left-to-right instead of right-to-left"
Cause: Missing bidi reordering or RTL direction setting Solution: For reportlab, apply get_display() from python-bidi. For python-docx, build paragraphs with the add_rtl_paragraph helper in Step 5 (sets <w:bidi/> on the paragraph and <w:rtl/> on the Hebrew runs). For WeasyPrint, ensure dir="rtl" on the HTML element.
Error: "Numbers and punctuation in wrong position"
Cause: Bidirectional text algorithm not handling mixed Hebrew/number content Solution: For reportlab, pass the whole logical string through get_display() in one call (see "Mixed Hebrew / Latin / Digit Lines"). In HTML-based tools (WeasyPrint), ensure proper unicode-bidi: isolate on embedded LTR spans. For DOCX/python-docx, do the OPPOSITE of the PDF fix: never call get_display() (Word reorders itself). Set <w:bidi/> on the paragraph, split the line into per-script runs, flag only the Hebrew runs <w:rtl/>, and set the w:cs font + w:szCs size on every run (see Step 5's add_rtl_paragraph).
Error: "Hebrew Word (.docx) renders with English on the wrong side, or my font/size is ignored"
Cause: The whole mixed line is in one run marked <w:rtl/> (English jumps), or the runs set only w:ascii/w:sz and never the complex-script w:cs/w:szCs (Hebrew ignores the font/size). A bare presence check for <w:rtl/> passes on a file that still renders broken, so verify the run structure, not just the flag. Solution: Use the add_rtl_paragraph helper in Step 5: per-script run splitting, rtl on Hebrew runs only, w:cs + w:szCs on every run.
{
"schemaVersion": "1.0",
"skill": "hebrew-document-generator",
"generated_at": "2026-06-17T00:40:00Z",
"claims": [
{
"claim_id": "python-libraries",
"claim": "Document generation uses reportlab, WeasyPrint, python-docx, python-bidi (0.6.x; canonical import `from bidi import get_display`; dropped Python below 3.9), and pptxgenjs (Node). All are current published packages",
"source_url": "https://pypi.org/project/python-bidi/",
"raw_snippet": "python-bidi 0.6.x, from bidi import get_display, drops Python below 3.9; reportlab; weasyprint; python-docx; pptxgenjs",
"fetched_at": "2026-06-17T00:35:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "references/hebrew-fonts.md"]
},
{
"claim_id": "israel-invoices-allocation-number",
"claim": "Under the Israel Invoices model, a tax invoice at/above the allocation-number threshold needs a Tax Authority allocation number (mispar haktzaa) printed on it or the buyer cannot deduct input VAT. The threshold phases down: 20,000 NIS in 2025, 10,000 NIS from January 2026, 5,000 NIS from 1 June 2026 (pre-VAT)",
"source_url": "https://www.gov.il/he/pages/israel-invoices",
"raw_snippet": "מודל חשבוניות ישראל: מספר הקצאה נדרש לחשבונית מס מעל הסף; 20,000 ש\"ח 2025, 10,000 ש\"ח מינואר 2026, 5,000 ש\"ח מ-1 ביוני 2026; בלי מספר הקצאה הקונה לא יכול לקזז מס תשומות",
"fetched_at": "2026-06-17T00:35:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "references/templates.md"]
},
{
"claim_id": "tax-invoice-fields-and-vat",
"claim": "A valid Israeli tax invoice (heshbonit mas) requires business name+address, 9-digit Osek Murshe number, sequential invoice number, date, customer name + TZ/company number, line items, subtotal, VAT at 18% (since Jan 2025), and total; allocation number at/above threshold",
"source_url": "https://he.wikipedia.org/wiki/חשבונית_מס",
"raw_snippet": "חשבונית מס: שם עסק, מספר עוסק מורשה, מספר חשבונית רץ, תאריך, שם הלקוח ות.ז./ח.פ., פריטים, מע\"מ 18%, סה\"כ; מע\"מ 18% מינואר 2025",
"fetched_at": "2026-06-17T00:35:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "references/templates.md"]
},
{
"claim_id": "hebrew-fonts-and-bidi",
"claim": "Hebrew document fonts (Heebo, Rubik, Assistant, Frank Ruhl Libre, David Libre, Noto Sans Hebrew) loaded via Google Fonts; Hebrew Unicode range U+0590-U+05FF; bidi reordering via get_display applied per line",
"source_url": "https://fonts.google.com/?subset=hebrew",
"raw_snippet": "Heebo Rubik Assistant Frank Ruhl Libre David Libre Noto Sans Hebrew; Hebrew Unicode U+0590-U+05FF; get_display per line",
"fetched_at": "2026-06-17T00:35:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "references/hebrew-fonts.md", "scripts/generate_doc.py"]
},
{
"claim_id": "reference-urls",
"claim": "Reference URLs cited by the skill",
"source_url": "https://he.wikipedia.org/wiki/חשבונית_מס",
"raw_snippet": "URLs: https://he.wikipedia.org/wiki/חשבונית_מס https://pypi.org/project/python-bidi/ https://fonts.google.com/?subset=hebrew https://doc.courtbouillon.org/weasyprint/stable/ https://docs.reportlab.com/ https://python-docx.readthedocs.io/ https://fonts.google.com/noto/specimen/Noto+Sans+Hebrew https://fonts.google.com/noto/specimen/Noto+Serif+Hebrew https://fonts.google.com/specimen/Assistant https://fonts.google.com/specimen/David+Libre https://fonts.google.com/specimen/Frank+Ruhl+Libre https://fonts.google.com/specimen/Heebo https://fonts.google.com/specimen/Rubik",
"fetched_at": "2026-06-17T00:35:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md"]
}
]
}
{
"author": "skills-il",
"version": "1.6.0",
"category": "localization",
"tags": {
"he": [
"מסמכים",
"עברית",
"PDF",
"RTL",
"חשבונית",
"חוזה",
"ישראל"
],
"en": [
"documents",
"hebrew",
"pdf",
"rtl",
"invoice",
"contract",
"israel"
]
},
"display_name": {
"he": "מחולל מסמכים בעברית",
"en": "Hebrew Document Generator"
},
"display_description": {
"he": "מייצרים מסמכים מקצועיים בעברית (PDF, DOCX/וורד ו-PPTX) עם פריסה נכונה מימין לשמאל, טיפול נכון בטקסט מעורב עברית ואנגלית (BiDi) וטיפוגרפיה עברית תקינה. השתמשו בכל פעם שצריך מסמך Word בעברית או מסמך וורד מעורב עברית-אנגלית, PDF בעברית או מצגת בעברית, כולל ניסוחים כמו \"מסמך Word בעברית\", \"קובץ docx בעברית\", \"להפיק חשבונית מס\" ו\"לנסח חוזה\", או תבניות ישראליות כמו חשבונית מס, חוזה, הצעת מחיר ופרוטוקול. השתמשו בסקיל הזה גם כשמסמך עברי נראה תקין על המסך או בתוך Claude אבל יוצא משובש, הפוך או שבור אחרי ייצוא ל-Word, עם מילים באנגלית, מספרים או סימני פיסוק בצד הלא נכון, בניסוחים כמו \"הטקסט בעברית הפוך ב-Word\", \"המסמך ב-Word יצא מבולגן\", \"לתקן עברית ב-Word\" או \"הפורמט בקובץ docx שבור\"; הפתרון הוא להפיק מחדש את קובץ ה-docx עם כיווניות RTL/BiDi ברמת הפסקה, ולא תיקון CSS של אתר. העדיפו את הסקיל הזה על פני סקיל ה-docx או ה-pdf הכללי רק כשהמסמך בעברית או מימין לשמאל, כי הם לא מגדירים RTL/BiDi והתוצאה היא עברית משובשת עם מילים באנגלית וסימני פיסוק במקום הלא נכון; למסמכים באנגלית בלבד תשתמשו בסקיל הכללי. מכסה reportlab, WeasyPrint, python-docx ו-pptxgenjs. לא מיועד לקריאת מסמכים קיימים או OCR (תשתמשו ב-hebrew-ocr-forms).",
"en": "Generate professional Hebrew documents (PDF, DOCX/Word, and PPTX) with correct right-to-left layout, mixed Hebrew-and-English bidi handling, and proper Hebrew typography. Use whenever the output is a Hebrew or mixed Hebrew/English Word document, Hebrew PDF, or Hebrew PowerPoint, including phrasings like \"Hebrew Word document\", \"Word document in Hebrew\", \"מסמך Word בעברית\", \"create a .docx in Hebrew\", \"lehafik heshbonit\", and \"litstor hozeh\", or Israeli templates such as Heshbonit Mas (tax invoice), Hozeh (contract), Hatza'at Mechir (proposal), or Protokol (meeting minutes). ALSO use this for the symptom where a Hebrew document looks correct on screen or in Claude but comes out scrambled, reversed, or broken after export to Word, with English words, numbers, or punctuation landing on the wrong side, phrased as \"Hebrew text reversed in Word\", \"my Hebrew Word file is broken\", \"fix Hebrew formatting in Word\", or \"the docx came out messed up\"; the fix is regenerating the .docx with paragraph-level RTL/bidi, NOT a web/CSS RTL change. Prefer this over the generic docx or pdf skills ONLY when the document is Hebrew or right-to-left, because those do not set RTL/bidi and produce scrambled Hebrew with English words and punctuation in the wrong place; for English-only documents use the generic skill. Covers reportlab, WeasyPrint, python-docx, and pptxgenjs. Do NOT use for OCR or reading existing documents (use hebrew-ocr-forms instead)."
},
"supported_agents": [
"claude-code",
"cursor",
"github-copilot",
"windsurf",
"opencode",
"codex",
"antigravity",
"gemini-cli"
]
}
{
"cycles": [
{
"version": "1.3.0",
"date": "2026-06-18",
"holdout_before": 1.0,
"holdout_after": 1.0,
"buckets": {
"improved": [],
"regressed": [],
"persistent_fail": [],
"stable_success": [
"create a Hebrew Word document for this contract",
"I need a .docx in Hebrew that mixes Hebrew and English without breaking",
"create a Word document summarizing Q3 sales (English only, should NOT trigger)"
]
},
"lessons": [
"DOCX root cause was never a missing <w:rtl/> (it was already emitted); the real defects were (a) the whole mixed line in one rtl-flagged run incl. Latin, and (b) no complex-script w:cs font / w:szCs size. Verified empirically by building the doc and inspecting word/document.xml.",
"Complex-script rule generalizes: bold/italic on Hebrew need w:bCs/w:iCs, not just w:b/w:i. Added bold+italic params.",
"Description broadened to win selection over the generic /mnt/skills/public/docx skill; kept the 'prefer' clause conditional on a Hebrew/RTL signal to avoid mis-routing English-only docx requests.",
"Coverage deferred (non-blocking MINOR): helper covers body paragraphs only; tables, headers/footers, numbered lists, and sectPr RTL flow are documented as a note but not implemented as helpers. Revisit if a user reports broken Hebrew tables."
]
},
{
"version": "1.3.1",
"date": "2026-06-22",
"holdout_before": 0.5,
"holdout_after": 1.0,
"buckets": {
"improved": [
"Hebrew doc looks fine in Claude but scrambled when opened in Word",
"fix Hebrew formatting in Word"
],
"regressed": [],
"persistent_fail": [],
"stable_success": [
"create a Hebrew Word document for this contract",
"set up RTL layout in my React/Tailwind Hebrew website (must NOT trigger)"
]
},
"lessons": [
"Routing gap was discovery-side, not content: body Troubleshooting already covered the scrambled-in-Word symptom thoroughly, but the description was goal-framed ('create a Hebrew Word doc') so symptom queries ('my Word doc is broken/reversed') mis-routed to the web-only hebrew-rtl-best-practices skill. Fix = add symptom/troubleshooting trigger phrasing to description (frontmatter EN + metadata display_description he/en).",
"SKILL.md frontmatter description is hard-capped at 1024 chars by validate-skill.sh; metadata.json display_description has no cap. Kept the fuller symptom phrasing in display_description (drives the site embedding/routing the user actually hits) and a trimmed 1015-char version in frontmatter.",
"Watch (non-blocking): description now contains the token 'web/CSS RTL' inside a negation ('NOT a web/CSS RTL change'). Embedding retrieval is negation-blind, so monitor that pure React/Tailwind RTL website queries still route to hebrew-rtl-best-practices and not here; document-signal density (Word/docx/PDF/PPTX/python-docx) should dominate. Revisit if a web-RTL query mis-routes.",
"Deferred (MINOR, scope-kept): did not add a symptom-framed FAQ pair to enriched content this cycle. Body Troubleshooting already explains it; FAQ does not feed routing embeddings. Revisit if users on the detail page still ask why the Word export breaks."
]
},
{
"version": "1.4.0",
"date": "2026-06-22",
"lessons": [
"User report (challenging mixed Hebrew/English medical doc): structure was correct (bidi on Hebrew paras, per-script runs, w:rtl on Hebrew only, David w:cs font) but ALL paragraphs incl. pure-English clinical lines got w:bidi + RIGHT unconditionally, so 7 English-only lines (lab values, drug/mutation rows) rendered right-aligned = the residual 'still looks RTL-broken' artifact.",
"Fix: add _para_is_rtl(text) and pick paragraph base direction per line: any Hebrew letter -> RTL+right (a Hebrew sentence may embed English); no Hebrew but Latin present -> LTR+left; all-neutral -> RTL. add_rtl_paragraph now emits w:bidi only for RTL paragraphs and aligns LEFT for pure-English.",
"Latent regex footgun fixed: the _HEB character class used a precomposed Hebrew presentation-form char (U+FB1D) that is invisible in source and can decompose to U+05D9+U+05B4, silently widening the class to U+05B4..U+FB4F and matching en-dash/CJK/etc. Replaced with explicit unicode-escape codepoints so neutral chars (the en-dash in 'Lobectomy - 9/2023') stay neutral and pure-English lines with dashes are detected LTR. Verified by extracting and executing the REAL SKILL.md code block against the user 8-line sample: all 8 base directions correct.",
"Still deferred (documented, not implemented): tables, headers/footers, numbered/bulleted lists, sectPr RTL flow. Per-script run split + base-direction are now both correct for body paragraphs only."
]
},
{
"version": "1.5.0",
"date": "2026-06-22",
"lessons": [
"MAJOR Word-bidi fix, proven against real Microsoft Word (the v1.4.0 helper's whole premise was wrong for Word). Word's bidi engine is STRICTER than Unicode/UBA: LibreOffice AND macOS CoreText/Quick Look both render the broken files CORRECTLY, hiding the bug, so they are useless as Word proxies. Only Word reveals it. Verified by round-tripping generated .docx through the user's Word.",
"Root cause: explicit per-run <w:rtl/> flags on the runs of a MIXED Hebrew+English paragraph. Word force-reverses any Latin/number caught in (or beside) an rtl-flagged run (7/2023 -> 2023/7, KI-67 flips) and mis-pairs parens. Fix: in a paragraph that contains any Latin letter, flag NO run rtl; the paragraph <w:bidi/> alone orders mixed lines correctly.",
"Pure-Hebrew paragraphs (labels/headings, no Latin letters, digits allowed) DO need <w:rtl/> on their Hebrew runs, or Word drifts a trailing colon (מחלות רקע:) and a leading section number (2. כותרת) to the wrong edge. So the rule is conditional on para_has_latin.",
"Unicode directional isolates (U+2066-2069) are NOT a usable fix: Word renders them as visible .notdef boxes in the David font (other viewers draw them zero-width, hiding it). Never insert directional control chars.",
"Digits treated as LTR-strong in _strong so a number forms its own LTR run (never rides in a Hebrew rtl run). Plus _shift_boundary_spaces: move a space at the end of an LTR run before an RTL run to the start of the RTL run, because Word trims a run's trailing whitespace at a direction boundary (gluing '2.' to the heading word); a leading space on the RTL run survives.",
"Carried from v1.4.0 (still true): base direction is per-paragraph (any Hebrew -> RTL+right; pure-English line -> LTR+left). Still deferred: tables, headers/footers, numbered/bulleted lists, sectPr RTL flow.",
"Process lesson: verify DOCX RTL ONLY in Microsoft Word. LibreOffice/CoreText/pdfplumber-vs-python-bidi all agreed the broken output was 'correct' and would have shipped the bug twice. Drive Word via the user (its AppleScript save-as is broken in current Mac builds and do-Visual-Basic was removed)."
]
},
{
"version": "1.6.0",
"date": "2026-06-23",
"lessons": [
"Leading section-number marker (2. / 10.) on an RTL line: Word floats its trailing period to the wrong side (renders as dot-then-number) because the marker is a separate LTR run. Fix _merge_list_marker: merge a leading 1-2-digit+period LTR run into the following Hebrew RTL run so the marker rides in RTL and the period stays after the number. Regex is strict (1-2 digits + period only) so a date like 13/01/2026 stays its own LTR run and is not reversed. Confirmed in real Word (N4 of the marker A/B test).",
"KNOWN LIMITATION (accepted, not a skill bug): a sentence ending in a Latin word + period in an RTL paragraph places the period at the visual left edge. This is native Word RTL behavior (identical to typing it by hand) and resists docx-level fixes: LRM (U+200E) after the period had no effect in Word, and isolates render as boxes. Left as-is; meaning and reading order are correct.",
"evidence verify-evidence.py DOES scan optimization-log.json. Keep bare percentages/NIS amounts out of lesson text (an illustrative KI-67 25% tripped the gate on the next cycle clone). Describe examples without a literal percent/amount."
]
}
]
}
Hebrew Fonts Reference
Recommended Fonts by Document Type
Sans-Serif (Modern Documents, Web, Invoices)
| Font | Weight Range | Google Fonts | Notes |
|---|---|---|---|
| Heebo | 100-900 | Link | Most popular Hebrew web font, excellent readability |
| Rubik | 300-900 | Link | Slightly rounded, friendly appearance |
| Assistant | 200-800 | Link | Clean, professional, good for business docs |
| Open Sans Hebrew | 300-800 | Bundled with Open Sans | Widely available, neutral style |
| Noto Sans Hebrew | 100-900 | Link | Part of Google Noto family, maximum language coverage |
Serif (Formal Documents, Contracts, Legal)
| Font | Weight Range | Google Fonts | Notes |
|---|---|---|---|
| Frank Ruhl Libre | 300-900 | Link | Classic Hebrew serif, ideal for legal/formal |
| David Libre | 400-700 | Link | Based on classic David font, elegant |
| Noto Serif Hebrew | 100-900 | Link | Full Unicode coverage |
System Fonts (No Installation Required)
| Font | Available On | Style |
|---|---|---|
| David | Windows, macOS (with Office) | Classic serif |
| Narkisim | Windows | Elegant serif |
| Arial Hebrew | macOS | Sans-serif |
| Courier New (Hebrew) | Windows, macOS | Monospace |
Font Pairing Recommendations
| Use Case | Primary (Headings) | Secondary (Body) |
|---|---|---|
| Business docs | Heebo Bold | Heebo Regular |
| Legal contracts | Frank Ruhl Libre Bold | David Libre Regular |
| Marketing material | Rubik Bold | Assistant Regular |
| Technical docs | Assistant SemiBold | Noto Sans Hebrew Regular |
| Presentations | Heebo Black | Heebo Light |
Font Stack CSS Examples
/* Modern business documents */
font-family: 'Heebo', 'Assistant', 'Arial Hebrew', sans-serif;
/* Formal/legal documents */
font-family: 'Frank Ruhl Libre', 'David Libre', 'David', serif;
/* Code with Hebrew comments */
font-family: 'Cousine', 'Noto Sans Mono', 'Courier New', monospace;Installation Instructions
macOS
# Via Homebrew (installs Google Fonts collection)
brew install --cask font-heebo font-rubik font-assistant
# Manual: Download TTF from Google Fonts, double-click to installLinux (Ubuntu/Debian)
# Noto fonts (includes Hebrew)
sudo apt-get install fonts-noto fonts-noto-extra
# Manual: Copy TTF files to ~/.local/share/fonts/ then run
fc-cache -fvWindows
Download TTF from Google Fonts, right-click and select "Install" or "Install for all users."
Python reportlab Registration
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont('Heebo', '/path/to/Heebo-Regular.ttf'))
pdfmetrics.registerFont(TTFont('Heebo-Bold', '/path/to/Heebo-Bold.ttf'))Typography Settings for Hebrew
- Font size: Use 12-14pt for body text (Hebrew needs slightly larger than Latin)
- Line height: 1.6-1.8 for body text
- Letter spacing: Never add letter-spacing to Hebrew text
- Word spacing: 0.03-0.05em can improve readability
- Paragraph spacing: 1.2-1.5em between paragraphs
Israeli Business Document Templates
Tax Invoice (Heshbonit Mas / חשבונית מס)
Legal Requirements (Israeli Tax Authority)
A tax invoice must include the following fields to be legally valid:
| Field | Hebrew | Required | Notes |
|---|---|---|---|
| Business name | שם העסק | Yes | As registered with Tax Authority |
| Business address | כתובת העסק | Yes | Full address |
| Osek Murshe number | מספר עוסק מורשה | Yes | 9-digit authorized dealer number |
| Invoice number | מספר חשבונית | Yes | Sequential, unique |
| Date of issue | תאריך הנפקה | Yes | DD/MM/YYYY format |
| Customer name | שם הלקוח | Yes | Individual or company |
| Customer ID | ת.ז. / ח.פ. | Yes | Customer name and TZ/company number, per current ITA invoicing rules |
| Allocation number | מספר הקצאה | Yes, at/above threshold | Israel Invoices model: required on a tax invoice at/above the threshold (20,000 NIS in 2025, 10,000 NIS from Jan 2026, 5,000 NIS from 1 June 2026, pre-VAT). Without it the buyer cannot deduct input VAT. Verify the current threshold. |
| Item description | תיאור הפריט | Yes | Clear description of goods/services |
| Quantity | כמות | Yes | Numeric |
| Unit price | מחיר ליחידה | Yes | Before VAT |
| Subtotal | סכום ביניים | Yes | Sum of all items |
| VAT amount | סכום מע"מ | Yes | Currently 18% |
| Total | סה"כ לתשלום | Yes | Subtotal + VAT |
VAT Rules
- Standard rate: 18% (since January 2025)
- VAT-exempt transactions: exports, certain financial services, fruits and vegetables
- Eilat zone: VAT-exempt for most goods and services
- Invoice must clearly separate the pre-VAT amount from the VAT amount
Contract (Hozeh / חוזה)
Standard Sections
| Section | Hebrew | Content |
|---|---|---|
| Preamble | מבוא | Date, parties, purpose |
| Definitions | הגדרות | Key terms used in the contract |
| Scope of work | היקף העבודה | Detailed description of deliverables |
| Payment terms | תנאי תשלום | Amounts, schedule, currency (NIS) |
| Duration | תקופת ההסכם | Start date, end date, renewal terms |
| Termination | ביטול ההסכם | Notice period, breach conditions |
| Confidentiality | סודיות | NDA clauses |
| IP rights | קניין רוחני | Ownership of deliverables |
| Liability | אחריות | Limitation of liability |
| Dispute resolution | יישוב סכסוכים | Jurisdiction (Israeli courts), arbitration |
| Signatures | חתימות | Both parties, date, witness if needed |
Standard Hebrew Legal Phrases
- "הואיל ו..." (Whereas...)
- "הוסכם והותנה בין הצדדים כדלקמן:" (It was agreed between the parties as follows:)
- "מבלי לגרוע מכלליות האמור לעיל" (Without derogating from the generality of the above)
- "למען הסר ספק" (For the avoidance of doubt)
Price Proposal (Hatza'at Mechir / הצעת מחיר)
Required Fields
| Field | Hebrew | Notes |
|---|---|---|
| Business details | פרטי העסק | Name, address, Osek number |
| Proposal number | מספר הצעה | Sequential |
| Date | תאריך | DD/MM/YYYY |
| Recipient | נמען | Customer name and details |
| Item list | רשימת פריטים | Description, quantity, unit price |
| Subtotal | סכום ביניים | Before VAT |
| VAT | מע"מ | 18% |
| Total | סה"כ | Including VAT |
| Validity period | תוקף ההצעה | Typically 30 days |
| Payment terms | תנאי תשלום | Net 30, installments, etc. |
| Notes | הערות | Special conditions |
Meeting Minutes (Protokol / פרוטוקול)
Standard Structure
| Section | Hebrew | Content |
|---|---|---|
| Header | כותרת | Meeting type, date, time, location |
| Attendees | משתתפים | Names and roles |
| Absent | נעדרים | Expected but absent members |
| Agenda | סדר יום | Numbered agenda items |
| Discussion | דיון | Summary of each agenda item |
| Decisions | החלטות | Numbered decisions made |
| Action items | משימות | Task, assignee, deadline |
| Next meeting | ישיבה הבאה | Date and preliminary agenda |
| Signatures | חתימות | Chair and secretary |
Receipt (Kabala / קבלה)
Required Fields
| Field | Hebrew | Notes |
|---|---|---|
| Business name | שם העסק | As registered |
| Receipt number | מספר קבלה | Sequential |
| Date | תאריך | DD/MM/YYYY |
| Amount received | סכום שהתקבל | In NIS |
| Payment method | אמצעי תשלום | Cash, check, transfer, credit card |
| Payer name | שם המשלם | Individual or company |
| Reference | אסמכתא | Check number, transfer reference |
#!/usr/bin/env python3
"""Generate Hebrew PDF documents with RTL support using reportlab.
Produces Israeli business documents (invoices, receipts) with proper
Hebrew typography, right-to-left text layout, and VAT calculations.
Usage:
python generate_doc.py --type invoice --output invoice.pdf
python generate_doc.py --type receipt --output receipt.pdf --font Heebo-Regular.ttf
python generate_doc.py --help
Requirements:
pip install reportlab python-bidi
"""
import argparse
import sys
from datetime import datetime
try:
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import mm
from reportlab.lib import colors
except ImportError:
print("Missing required dependency. Install with:", file=sys.stderr)
print(" pip install reportlab", file=sys.stderr)
sys.exit(1)
try:
# python-bidi 0.5.0+ exposes get_display at the top level.
# The old `from bidi.algorithm import get_display` path was removed.
from bidi import get_display
except ImportError:
print("Missing required dependency. Install with:", file=sys.stderr)
print(" pip install python-bidi # 0.6.x, requires Python 3.9+", file=sys.stderr)
sys.exit(1)
# Israeli VAT rate
VAT_RATE = 0.18
def register_hebrew_font(font_path, font_name="HebrewFont"):
"""Register a Hebrew TTF font with reportlab.
Args:
font_path: Path to the TTF font file.
font_name: Name to register the font under.
Returns:
The registered font name.
"""
try:
pdfmetrics.registerFont(TTFont(font_name, font_path))
return font_name
except Exception as e:
print(f"Warning: Could not register font {font_path}: {e}",
file=sys.stderr)
print("Falling back to Helvetica (Hebrew may not render correctly)",
file=sys.stderr)
return "Helvetica"
def hebrew_text(text):
"""Apply bidi algorithm for correct RTL display.
Args:
text: Hebrew text string.
Returns:
Display-reordered string for RTL rendering.
"""
return get_display(text)
def draw_hebrew_line(c, x, y, text, font_name, font_size):
"""Draw a right-aligned Hebrew text line on the canvas.
Args:
c: reportlab Canvas object.
x: Right edge x-coordinate.
y: y-coordinate.
text: Hebrew text to draw.
font_name: Registered font name.
font_size: Font size in points.
"""
c.setFont(font_name, font_size)
c.drawRightString(x, y, hebrew_text(text))
def generate_invoice(filename, font_name, business_info=None):
"""Generate a sample Hebrew tax invoice (Heshbonit Mas).
Args:
filename: Output PDF file path.
font_name: Registered Hebrew font name.
business_info: Optional dict with business details.
"""
if business_info is None:
business_info = {
"name": "חברת דוגמה בע\"מ",
"address": "רחוב הרצל 1, תל אביב",
"osek_number": "123456789",
"invoice_number": "1001",
}
c = canvas.Canvas(filename, pagesize=A4)
width, height = A4
right_margin = width - 20 * mm
left_margin = 20 * mm
# Header
draw_hebrew_line(c, right_margin, height - 25 * mm,
"חשבונית מס", font_name, 22)
draw_hebrew_line(c, right_margin, height - 35 * mm,
business_info["name"], font_name, 14)
draw_hebrew_line(c, right_margin, height - 42 * mm,
business_info["address"], font_name, 10)
draw_hebrew_line(c, right_margin, height - 49 * mm,
f"עוסק מורשה: {business_info['osek_number']}",
font_name, 10)
# Invoice details
today = datetime.now().strftime("%d/%m/%Y")
draw_hebrew_line(c, right_margin, height - 60 * mm,
f"חשבונית מס׳: {business_info['invoice_number']}",
font_name, 11)
draw_hebrew_line(c, right_margin, height - 67 * mm,
f"תאריך: {today}", font_name, 11)
# Separator line
c.setStrokeColor(colors.black)
c.line(left_margin, height - 73 * mm, right_margin, height - 73 * mm)
# Sample line items
items = [
("שירותי ייעוץ - חודש ינואר", 1, 5000.00),
("פיתוח תוכנה - שלב א׳", 1, 12000.00),
("תחזוקה שוטפת", 3, 800.00),
]
# Table header
y = height - 82 * mm
c.setFont(font_name, 10)
c.drawRightString(right_margin, y, hebrew_text("תיאור"))
c.drawString(left_margin + 80 * mm, y, hebrew_text("כמות"))
c.drawString(left_margin + 50 * mm, y, hebrew_text("מחיר"))
c.drawString(left_margin, y, hebrew_text("סה\"כ"))
c.line(left_margin, y - 2 * mm, right_margin, y - 2 * mm)
# Table rows
y -= 9 * mm
subtotal = 0.0
for desc, qty, price in items:
total = qty * price
subtotal += total
c.drawRightString(right_margin, y, hebrew_text(desc))
c.drawString(left_margin + 80 * mm, y, str(qty))
c.drawString(left_margin + 50 * mm, y, f"{price:,.2f}")
c.drawString(left_margin, y, f"{total:,.2f}")
y -= 7 * mm
# Totals
c.line(left_margin, y, right_margin, y)
y -= 8 * mm
vat = subtotal * VAT_RATE
grand_total = subtotal + vat
draw_hebrew_line(c, left_margin + 60 * mm, y,
f"סכום ביניים: {subtotal:,.2f} ש\"ח",
font_name, 11)
y -= 7 * mm
draw_hebrew_line(c, left_margin + 60 * mm, y,
f"מע\"מ (18%): {vat:,.2f} ש\"ח",
font_name, 11)
y -= 7 * mm
draw_hebrew_line(c, left_margin + 60 * mm, y,
f"סה\"כ לתשלום: {grand_total:,.2f} ש\"ח",
font_name, 13)
c.save()
print(f"Generated invoice: {filename}")
def generate_receipt(filename, font_name):
"""Generate a sample Hebrew receipt (Kabala).
Args:
filename: Output PDF file path.
font_name: Registered Hebrew font name.
"""
c = canvas.Canvas(filename, pagesize=A4)
width, height = A4
right_margin = width - 20 * mm
draw_hebrew_line(c, right_margin, height - 25 * mm,
"קבלה", font_name, 22)
draw_hebrew_line(c, right_margin, height - 40 * mm,
"חברת דוגמה בע\"מ", font_name, 14)
today = datetime.now().strftime("%d/%m/%Y")
draw_hebrew_line(c, right_margin, height - 55 * mm,
f"תאריך: {today}", font_name, 11)
draw_hebrew_line(c, right_margin, height - 62 * mm,
"קבלה מס׳: 5001", font_name, 11)
draw_hebrew_line(c, right_margin, height - 72 * mm,
"התקבל סך: 19,890.00 ש\"ח", font_name, 13)
draw_hebrew_line(c, right_margin, height - 80 * mm,
"אמצעי תשלום: העברה בנקאית", font_name, 11)
c.save()
print(f"Generated receipt: {filename}")
def main():
parser = argparse.ArgumentParser(
description="Generate Hebrew PDF documents with RTL support"
)
parser.add_argument(
"--type", choices=["invoice", "receipt"], default="invoice",
help="Document type to generate (default: invoice)"
)
parser.add_argument(
"--output", default="output.pdf",
help="Output PDF file path (default: output.pdf)"
)
parser.add_argument(
"--font", default=None,
help="Path to Hebrew TTF font file (optional)"
)
args = parser.parse_args()
font_name = "Helvetica"
if args.font:
font_name = register_hebrew_font(args.font)
if args.type == "invoice":
generate_invoice(args.output, font_name)
elif args.type == "receipt":
generate_receipt(args.output, font_name)
if __name__ == "__main__":
main()
מחולל מסמכים בעברית
הנחיות
שלב 1: בחרו את פורמט הפלט
| פורמט | ספרייה | מתאים ל- | תמיכת RTL |
|---|---|---|---|
| reportlab | חשבוניות, מסמכי מס, טפסים להדפסה | רושמים גופן עברי, משתמשים ב-canvas.drawRightString() | |
| WeasyPrint | מסמכים מעוצבים מ-HTML/CSS | מובנה דרך dir="rtl" ב-HTML | |
| DOCX | python-docx | חוזים, הצעות מחיר, פרוטוקולים | מגדירים bidi בפסקה; מפצלים runs מעורבים, מגדירים גופן w:cs ו-w:rtl רק על ה-runs העבריים |
| PPTX | pptxgenjs (Node) | מצגות, שקפים | תיבות טקסט RTL עם rtlMode: true |
שלב 2: התקינו תלויות וגופנים עבריים
יצירת PDF בפייתון:
pip install reportlab weasyprintיצירת DOCX בפייתון:
pip install python-docx python-bidiיצירת PPTX ב-Node.js:
npm install pptxgenjsגופנים עבריים מומלצים (מתקינים על המערכת):
| גופן | סגנון | מתאים ל- | מקור |
|---|---|---|---|
| Heebo | סנס-סריף, מודרני | מסמכי ווב, חשבוניות | Google Fonts |
| David | סריף קלאסי | חוזים משפטיים, מכתבים רשמיים | מערכת (Windows/macOS) |
| Narkisim | סריף, אלגנטי | הצעות מחיר, הזמנות | מערכת (Windows) |
| Frank Ruehl | סריף מסורתי | אקדמי, ספרותי | Google Fonts (Frank Ruhl Libre) |
| Rubik | סנס-סריף, מעוגל | מצגות, שיווק | Google Fonts |
| Assistant | סנס-סריף, נקי | התכתבות עסקית | Google Fonts |
תסתכלו על references/hebrew-fonts.md לקישורי הורדה והוראות התקנה.
שלב 3: PDF בעברית עם reportlab
תסתכלו על scripts/generate_doc.py לפייפליין המלא של היצירה.
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import mm
from bidi import get_display # python-bidi 0.6.x; ראו הערה למטה
# רישום גופן עברי
pdfmetrics.registerFont(TTFont('Heebo', 'Heebo-Regular.ttf'))
pdfmetrics.registerFont(TTFont('Heebo-Bold', 'Heebo-Bold.ttf'))
def create_hebrew_pdf(filename, title, content_lines):
c = canvas.Canvas(filename, pagesize=A4)
width, height = A4
# כותרת -- יישור לימין עבור RTL
c.setFont('Heebo-Bold', 18)
hebrew_title = get_display(title)
c.drawRightString(width - 20*mm, height - 30*mm, hebrew_title)
# שורות תוכן
c.setFont('Heebo', 12)
y = height - 50*mm
for line in content_lines:
display_line = get_display(line)
c.drawRightString(width - 20*mm, y, display_line)
y -= 7*mm
c.save()נקודות חשובות ל-reportlab בעברית:
- תמיד להשתמש ב-
get_display()מ-python-bidi לסידור מחדש של תווים - להשתמש ב-
drawRightString()לטקסט RTL מיושר לימין - לרשום גופני TTF עבריים במפורש - ל-reportlab אין תמיכה מובנית בעברית
- לקבוע גובה שורה של לפחות 1.5 מגודל הגופן לקריאות בעברית
- ייבוא python-bidi: הייבוא הקנוני והמומלץ הוא
from bidi import get_display(העליון). הנתיב הישןfrom bidi.algorithm import get_displayעדיין נטען ב-0.6.x כמודול תאימות לאחור, אבל עדיף להשתמש בייבוא העליון. גרסה 0.6.x גם הפסיקה לתמוך בפייתון מתחת ל-3.9. - טקסט רב-שורתי:
drawRightString()מצייר שורה אחת ולא גולש. לכל טקסט גוף ארוך משורה אחת, תשתמשו ב-flowable מסוגParagraph(מ-reportlab.platypus) עםParagraphStyleמיושר לימין ו-RTL. הסקריפט המצורףscripts/generate_doc.pyמשתמש ב-drawRightStringשורה-שורה למסמכים קומפקטיים בפריסה קבועה (חשבוניות, קבלות); הוא יחתוך מחרוזות עבריות ארוכות. כדאי לעבור ל-Paragraphול-flowables של platypus לחוזים או לכל טקסט גוף שגולש.
שורות מעורבות עברית / לטינית / ספרות
הכשל הנפוץ ביותר ב-RTL במסמכים מיוצרים הוא שורה שמערבת תיאור בעברית עם מספרים LTR וסמל מטבע, למשל שורת פריט בחשבונית. get_display() מטפל בסידור הדו-כיווני, אבל צריך להעביר את כל המחרוזת הלוגית בקריאה אחת כדי שהאלגוריתם יראה את ההקשר המלא:
from bidi import get_display
# סדר לוגי: תיאור בעברית, אחר כך כמות, מחיר ליחידה, מטבע
line = 'ייעוץ טכני (3 שעות) - 1,500.00 ש"ח'
c.setFont('Heebo', 11)
c.drawRightString(width - 20 * mm, y, get_display(line))הספרות, הפסיק, הנקודה והסוגריים נשארים כולם במיקום ה-LTR הנכון כי האלגוריתם הדו-כיווני פותר אותם ביחס לעברית שמסביב. אל תפצלו את השורה לחלקים ותסדרו אותם בעצמכם, ואל תקראו ל-get_display() רק על החלק העברי, שתי הגישות שוברות את סדר המספרים.
שלב 4: PDF בעברית עם WeasyPrint
from weasyprint import HTML
html_content = """
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="utf-8">
<style>
@font-face {
font-family: 'Heebo';
src: url('Heebo-Regular.ttf');
}
body {
font-family: 'Heebo', sans-serif;
direction: rtl;
font-size: 12pt;
line-height: 1.7;
}
h1 { font-size: 18pt; text-align: start; }
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #333;
padding: 6px 10px;
text-align: start;
}
</style>
</head>
<body>
<h1>חשבונית מס</h1>
<!-- תוכן המסמך כאן -->
</body>
</html>
"""
HTML(string=html_content).write_pdf('invoice.pdf')היתרונות של WeasyPrint לעברית:
- תמיכה מלאה ב-CSS כולל תכונות לוגיות
- RTL מובנה דרך תכונת
dirב-HTML - טבלאות מוצגות נכון ב-RTL
- תמיכה ב-
@font-faceלגופנים עבריים מותאמים
שלב 5: DOCX בעברית עם python-docx
ב-DOCX טקסט מעורב עברית/אנגלית נשבר הכי הרבה, ומנוע ה-bidi של Microsoft Word מחמיר יותר מתקן Unicode. LibreOffice, תצוגה מקדימה של macOS ורוב המציגים מרנדרים תוצאה סלחנית שמסתירה באגים ייחודיים ל-Word, אז תמיד בדקו ב-Word עצמו ולא במציג חלופי. ארבעה כללים, כל אחד נלמד מול Word אמיתי:
1. כל פסקה עברית נושאת <w:bidi/> (כיוון בסיס RTL); שורה באנגלית בלבד (ערך מעבדה, שם תרופה, שורה קלינית באנגלית) מקבלת בסיס LTR ויישור לשמאל. העוזר קובע זאת לכל פסקה לפי האם השורה מכילה עברית, כך ששורות באנגלית בלבד לא נדחקות לשוליים הימניים במסמך עברי. 2. אל תשימו `<w:rtl/>` על ה-runs של פסקה מעורבת עברית+אנגלית. זו המלכודת הגדולה ביותר ב-Word. Word מכבד <w:rtl/> בקפדנות: כל לטינית או מספר שנלכד ב-run מסומן rtl (או לידו) נהפך, כך ש-7/2023 מודפס 2023/7, קוד KI-67 מוטבע מתהפך, והסוגריים סביב קבוצה מעורבת כמו (גסטרית, KI-67) לא מזדווגים נכון. בפסקה מעורבת ה-<w:bidi/> של הפסקה כבר מסדר את השורה, השאירו כל run בלי דגל. 3. סמנו `<w:rtl/>` רק על runs עבריים של פסקה ללא אותיות לטיניות (תווית או כותרת עברית טהורה, ספרות מותרות). שם הדגל מעגן נקודתיים נגררות (מחלות רקע:) לקצה השמאלי. סמן כותרת מספרי מוביל (2., 10.) ממוזג בנוסף אל ה-run העברי (_merge_list_marker) כדי שהנקודה שלו לא תתהפך ל-.2; תאריך כמו 13/01/2026 נשאר run מסוג LTR נפרד כדי ש-Word לא יהפוך אותו. הפיצול נשאר לפי סקריפט כדי שכל run יקבל את גופן הסקריפט המורכב הנכון. 4. כל run מגדיר את גופן הסקריפט המורכב (w:cs) ואת הגודל (w:szCs). עברית היא "סקריפט מורכב" במודל של Word, אז w:ascii/w:sz לבדם לעולם לא חלים על התווים העבריים. השמטת w:cs/w:szCs היא הסיבה הנפוצה ביותר ל"הגופן והגודל שהגדרתי לא עשו כלום והעברית נראית שבורה". מודגש ונטוי זהים: w:b/w:i משפיעים רק על לטינית, צריך גם w:bCs/w:iCs. לעולם אל תכניסו תווי בידוד כיווניים של Unicode (U+2066-2069) או סימונים כדי לכפות סדר, Word מרנדר אותם כריבועי `.notdef` גלויים בגופן David גם כשמציגים אחרים מסתירים אותם.
import re
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
# בלוק עברי + צורות הצגה עבריות. משמש לבחירת כיוון של כל run.
_HEB = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
_LIST_MARKER = re.compile(r'^\d{1,2}\.$') # 1-2 digit list marker, e.g. "2."
def _strong(ch):
"""True לאות עברית, False לתו חזק מסוג LTR (לטינית או ספרת ASCII), None לתו
ניטרלי. ספרות נחשבות LTR כדי שמספר לא ירכב בתוך run עברי (Word הופך מספר
שנלכד ב-run מסומן rtl)."""
if _HEB.match(ch):
return True
if ch.isascii() and ch.isalnum():
return False
return None
def _split_by_script(text):
"""מפצל מחרוזת מעורבת ל-runs בצורת (segment, is_rtl).
כיוון כל run נקבע לפי התווים החזקים שבו; תווים ניטרליים (רווחים, ספרות,
פיסוק) נצמדים ל-run הנוכחי. ניטרלים בתחילת המחרוזת יורשים את כיוון התו
החזק הראשון בכל המחרוזת (ברירת מחדל RTL למחרוזת ניטרלית לגמרי במסמך עברי),
כך ששורה לטינית בעיקרה שמתחילה בספרה או בסוגר לא מסומנת בטעות RTL. הפיצול
מקבץ תווים כך שכל run יקבל את גופן הסקריפט המורכב הנכון; ב-Word כיוון ה-run
נקבע לפי כללי ה-rtl ב-add_rtl_paragraph, ולא לפי הפיצול הזה לבדו.
"""
default_rtl = next((s for s in (_strong(c) for c in text) if s is not None), True)
segments, buf, buf_rtl = [], '', None
for ch in text:
s = _strong(ch)
kind = s if s is not None else (buf_rtl if buf_rtl is not None else default_rtl)
if buf_rtl is None or kind == buf_rtl:
buf, buf_rtl = buf + ch, kind
else:
segments.append((buf, buf_rtl))
buf, buf_rtl = ch, kind
if buf:
segments.append((buf, buf_rtl))
return segments
def _shift_boundary_spaces(segments):
"""מעביר רווח שבסוף run מסוג LTR שקודם ל-run מסוג RTL אל תחילת ה-run ה-RTL.
Word גוזם רווח נגרר של run בגבול כיוון, מה שמצמיד מספר מוביל לכותרת
("2.\u05db\u05d5\u05ea\u05e8\u05ea"); רווח מוביל ב-run ה-RTL שורד ומחזיר את הרווח.
בלי זה כותרות עבריות ממוספרות מאבדות את הרווח אחרי "N.".
"""
out = [[seg, rtl] for seg, rtl in segments]
for i in range(len(out) - 1):
seg, rtl = out[i]
nseg, nrtl = out[i + 1]
if rtl is False and nrtl is True and seg.endswith(' '):
stripped = seg.rstrip(' ')
out[i][0] = stripped
out[i + 1][0] = seg[len(stripped):] + nseg
return [(s, r) for s, r in out if s]
def _merge_list_marker(segments):
"""סמן רשימה מספרי מוביל קצר ("2.", "10.") בשורת RTL חייב להיות חלק מה-run
העברי, אחרת Word מציף את הנקודה שלו לצד הלא נכון (".2"). ממזגים run סמן
מסוג LTR מוביל אל ה-run העברי שאחריו. מתאים רק ל-1-2 ספרות + נקודה, לעולם
לא לתאריך כמו 13/01/2026 (שחייב להישאר run מסוג LTR כדי ש-Word לא יהפוך אותו)."""
if (len(segments) >= 2 and segments[0][1] is False
and _LIST_MARKER.match(segments[0][0].strip())
and segments[1][1] is True):
return [(segments[0][0] + segments[1][0], True)] + list(segments[2:])
return list(segments)
def _para_is_rtl(text):
"""בוחר את כיוון הבסיס של הפסקה במסמך עברי.
אות עברית כלשהי -> בסיס RTL: משפט עברי משלב לעיתים קרובות מונחים באנגלית,
שמות תרופות או מספרים, וחייב עדיין לזרום מימין לשמאל. אין עברית אבל יש לטינית
-> בסיס LTR, כך ששורה באנגלית בלבד (ערך מעבדה, שורה קלינית באנגלית) תיושר
לשמאל במקום להידחק לשוליים הימניים. הכול ניטרלי (ספרות/פיסוק) -> RTL, ברירת
המחדל של המסמך. זה התיקון ל"שורות באנגלית בלבד יוצאות מיושרות לימין והמסמך
עדיין נראה שבור ב-RTL".
"""
if _HEB.search(text):
return True
if any(ch.isascii() and ch.isalnum() for ch in text):
return False
return True
def add_rtl_paragraph(doc, text, font='David', size=12, bold=False, italic=False,
heading_level=None):
"""מוסיף פסקה שמרנדרת נכון טקסט מעורב עברית/לטינית/ספרות, ובוחרת אוטומטית
כיוון בסיס RTL או LTR לפי האם השורה מכילה עברית.
מכסה פסקאות גוף בלבד. תאי טבלה, כותרות עליונות/תחתונות ורשימות ממוספרות הם
"סיפורים" נפרדים במסמך: החילו את אותה לוגיקה על כל אחת מהפסקאות שלהם, והוסיפו
`<w:bidi/>` ל-`sectPr` של המקטע עבור עמוד RTL מלא.
"""
p = doc.add_heading(level=heading_level) if heading_level else doc.add_paragraph()
# (1) כיוון בסיס של הפסקה: RTL כשהשורה מכילה עברית כלשהי (משפט עברי משלב
# לעיתים אנגלית וחייב עדיין לזרום מימין לשמאל); LTR לשורה לטינית בלבד,
# כך ששורה באנגלית בלבד תיושר לשמאל במקום להידחק לשוליים הימניים.
base_rtl = _para_is_rtl(text)
pPr = p._p.get_or_add_pPr()
if base_rtl:
pPr.append(pPr.makeelement(qn('w:bidi'), {}))
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT if base_rtl else WD_ALIGN_PARAGRAPH.LEFT
# פסקה שיש בה אות לטינית כלשהי היא "מעורבת": לעולם לא מסמנים rtl על ה-runs
# שלה (כלל 2). פסקה עם עברית בלבד (+ספרות/פיסוק) היא "טהורה": ה-runs העבריים
# שלה כן מקבלים rtl, כדי לעגן נקודתיים נגררות ומספרים מובילים.
para_has_latin = any(ch.isascii() and ch.isalpha() for ch in text)
for segment, is_rtl in _shift_boundary_spaces(_merge_list_marker(_split_by_script(text))):
run = p.add_run(segment)
rPr = run._r.get_or_add_rPr()
# ילדי rPr חייבים להישאר בסדר הסכמה של OOXML: rFonts, b, bCs, i, iCs, sz, szCs, rtl
rPr.append(rPr.makeelement(qn('w:rFonts'), {
qn('w:ascii'): font, qn('w:hAnsi'): font, qn('w:cs'): font}))
if bold:
# הדגשה דורשת גם w:b (לטינית) וגם w:bCs (סקריפט מורכב / עברית)
rPr.append(rPr.makeelement(qn('w:b'), {}))
rPr.append(rPr.makeelement(qn('w:bCs'), {}))
if italic:
# נטוי דורש באותו אופן גם w:i וגם w:iCs עבור עברית
rPr.append(rPr.makeelement(qn('w:i'), {}))
rPr.append(rPr.makeelement(qn('w:iCs'), {}))
# (3) גודל סקריפט מורכב, כדי שהגודל יחול על העברית
rPr.append(rPr.makeelement(qn('w:sz'), {qn('w:val'): str(size * 2)}))
rPr.append(rPr.makeelement(qn('w:szCs'), {qn('w:val'): str(size * 2)}))
# (2) מסמנים rtl רק על runs עבריים של פסקה ללא אותיות לטיניות. בפסקה
# מעורבת עברית+אנגלית אף run לא מסומן, אחרת Word הופך את המספרים/
# הלטינית המוטבעים ומשבש סוגריים. ה-<w:bidi/> של הפסקה לבדו מסדר
# שורות מעורבות נכון.
if is_rtl and not para_has_latin:
rPr.append(rPr.makeelement(qn('w:rtl'), {}))
return p
doc = Document()
doc.styles['Normal'].font.name = 'David'
doc.styles['Normal'].font.size = Pt(12)
add_rtl_paragraph(doc, 'חוזה שירותים', size=18, bold=True, heading_level=1)
add_rtl_paragraph(doc, 'ההסכם נחתם בין חברת Acme בע"מ לבין הלקוח (גרסה 2).')
doc.save('contract.docx')אל תקראו ל-`get_display()` על טקסט של DOCX. בניגוד ל-reportlab (שמצייר גליפים במיקום קבוע ולכן זקוק ל-python-bidi כדי לסדר אותם), Word מפעיל את אלגוריתם ה-bidi בעצמו. עיבוד מקדים של מחרוזת עם get_display() והעברתה ל-python-docx מפעילים את האלגוריתם פעמיים ומשבשים את התוצאה. get_display() שייך לנתיב ה-PDF בלבד.
העוזר הזה מכסה פסקאות גוף. טבלאות, כותרות עליונות/תחתונות ורשימות ממוספרות/תבליטים הם "סיפורים" נפרדים במסמך שהעוזר לא מגיע אליהם: החילו את אותה לוגיקה של <w:bidi/> + פיצול runs לפי סקריפט על כל אחת מהפסקאות שלהם, והוסיפו <w:bidi/> ל-sectPr של המקטע עבור זרימת עמוד RTL מלאה.
שלב 6: PPTX בעברית עם pptxgenjs
const pptxgen = require('pptxgenjs');
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9';
pptx.rtlMode = true;
const slide = pptx.addSlide();
// כותרת בעברית
slide.addText('סקירה רבעונית', {
x: 0.5, y: 0.5, w: '90%', h: 1.0,
fontSize: 28,
fontFace: 'Heebo',
color: '1a1a2e',
align: 'right',
rtlMode: true,
bold: true,
});
// נקודות תבליט בעברית
slide.addText([
{ text: 'תוצאות כספיות', options: { bullet: true, rtlMode: true } },
{ text: 'יעדים לרבעון הבא', options: { bullet: true, rtlMode: true } },
{ text: 'סיכום פעילות', options: { bullet: true, rtlMode: true } },
], {
x: 0.5, y: 2.0, w: '90%', h: 3.0,
fontSize: 18,
fontFace: 'Heebo',
align: 'right',
rtlMode: true,
});
pptx.writeFile({ fileName: 'quarterly-review.pptx' });שלב 7: תבניות מסמכים עסקיים ישראליים
תסתכלו על references/templates.md למפרטי שדות מלאים לכל סוג מסמך.
| תבנית | שם בעברית | שדות נדרשים |
|---|---|---|
| חשבונית מס | חשבונית מס | שם עסק, מספר עוסק מורשה, תאריך, פריטים, מע"מ (18%), סה"כ |
| חוזה | חוזה | צדדים, ת.ז./ח.פ., תנאים, חתימות, תאריך |
| הצעת מחיר | הצעת מחיר | פרטי עסק, תמחור מפורט, תוקף, תנאים |
| פרוטוקול | פרוטוקול | תאריך, משתתפים, סדר יום, החלטות, משימות |
| קבלה | קבלה | שם עסק, מספר קבלה, סכום, אמצעי תשלום, תאריך |
חשבונית מס - שדות שהחוק הישראלי דורש:
- שם העסק וכתובת
- מספר עוסק מורשה
- מספר חשבונית רץ
- תאריך הנפקה
- שם הלקוח ות.ז./ח.פ.
- פריטים עם תיאור, כמות, מחיר ליחידה
- סכום ביניים, מע"מ 18%, וסה"כ בש"ח
- מספר הקצאה במודל "חשבוניות ישראל" לחשבונית מס בסכום שמעל הסף הנוכחי. הסף יורד בהדרגה (20,000 ש"ח ב-2025, 10,000 ש"ח מינואר 2026, 5,000 ש"ח מ-1 ביוני 2026, לפני מע"מ). בסכום שמעל הסף הקונה אינו יכול לקזז את מס התשומות אלא אם המוכר קיבל מספר הקצאה מרשות המסים והדפיס אותו על החשבונית. הוסיפו שדה מספר הקצאה לכל תבנית חשבונית והתייחסו לסף כרגיש לזמן (ודאו את הסכום העדכני מול רשות המסים).
דוגמאות
דוגמה 1: חשבונית מס כ-PDF
המשתמש אומר: "צור חשבונית מס בעברית כ-PDF לעסק שלי" תוצאה: יוצרים PDF בגודל A4 עם reportlab או WeasyPrint, עם פריסת RTL, כותרת עסק, מספר חשבונית רץ, טבלת פריטים, חישוב מע"מ 18%, סכומים בש"ח עם סמל שקל, וגופן עברי לאורך כל המסמך.
דוגמה 2: חוזה DOCX בעברית
המשתמש אומר: "נסח חוזה שירותים בעברית כמסמך Word" תוצאה: משתמשים ב-python-docx עם העוזר add_rtl_paragraph (שלב 5): פסקאות <w:bidi/>, פיצול runs לפי סקריפט כך שאנגלית/מספרים מוטבעים נשארים במקומם, גופן w:cs וגודל w:szCs, גופן David, יישור RTL, סעיפים מובנים (צדדים, היקף, תנאי תשלום, ביטול, חתימות), וניסוח משפטי עברי תקני.
דוגמה 3: מצגת בעברית
המשתמש אומר: "הכן מצגת בעברית לסקירה הרבעונית שלנו" תוצאה: משתמשים ב-pptxgenjs עם rtlMode מופעל, גופן Heebo, תיבות טקסט מיושרות לימין, נקודות תבליט ב-RTL, כותרות שקפים בעברית, ופריסה מקצועית.
דוגמה 4: מסמכים באצווה
המשתמש אומר: "צור 50 חשבוניות בעברית מקובץ CSV" תוצאה: קוראים נתוני CSV, עוברים על השורות, משתמשים ב-scripts/generate_doc.py כדי לייצר קובצי PDF בודדים עם מספרי חשבונית ייחודיים, פרטי לקוח ופריטים לכל שורה.
משאבים מצורפים
סקריפטים
scripts/generate_doc.py- יצירת מסמכי PDF בעברית עם reportlab: רישום גופנים עבריים, סידור טקסט RTL עם python-bidi, הפקת מסמכים עסקיים ישראליים (חשבוניות, קבלות) עם חישובי מע"מ ופורמט ש"ח. הרצה:python scripts/generate_doc.py --help
קובצי עזר
references/hebrew-fonts.md- קטלוג גופנים עבריים עם גופנים מומלצים לסוגי מסמכים שונים (סנס-סריף, סריף, מונוספייס), קישורי הורדה מ-Google Fonts, טבלת זמינות של גופני מערכת, הצעות לזיווג גופנים, והוראות התקנה ל-macOS, Linux ו-Windows.references/templates.md- תבניות מסמכים עסקיים ישראליים עם שדות נדרשים לכל סוג מסמך (חשבונית מס, חוזה, הצעת מחיר, קבלה, פרוטוקול), דרישות החוק הישראלי לחשבוניות, כללי מע"מ, וניסוח עסקי סטנדרטי בעברית.
קישורי עזר
| מקור | כתובת | מה לבדוק |
|---|---|---|
| תיעוד reportlab | https://docs.reportlab.com/ | API של Canvas, flowables של platypus, רישום גופנים |
| תיעוד WeasyPrint | https://doc.courtbouillon.org/weasyprint/stable/ | המרת HTML/CSS ל-PDF, תמיכת RTL, @font-face |
| תיעוד python-docx | https://python-docx.readthedocs.io/ | מודל המסמך, runs, תכונות פסקה |
| python-bidi (PyPI) | https://pypi.org/project/python-bidi/ | גרסה נוכחית, נתיב ייבוא, יומן שינויים |
| דרישות חשבונית מס בישראל | https://he.wikipedia.org/wiki/חשבונית_מס | שדות חובה בחשבונית מס; כדאי להצליב מול כללי רשות המסים העדכניים |
לדרישות משפטיות מחייבות תמיד כדאי לאמת מול ההנחיות העדכניות של רשות המסים, ערך הוויקיפדיה הוא נקודת התמצאות ולא מקור סמכות.
שרתי MCP מומלצים
אין שרת MCP שמתאים לסקיל הזה. יצירת מסמכים בעברית רצה כולה דרך ספריות פייתון ו-Node מקומיות (reportlab, WeasyPrint, python-docx, pptxgenjs); אין שירות חיצוני לעטוף כשרת MCP. תשתמשו בסקריפטים המצורפים ובקוד שבחלק ההנחיות ישירות.
מלכודות נפוצות
- צריך להפעיל את
get_display()שורה-שורה בזמן הציור, מיד לפניdrawRightString(), ולא פעם אחת על מסמך או בלוק רב-שורתי שלם. האלגוריתם הדו-כיווני אינו אידמפוטנטי: הרצה שלו על טקסט שכבר סודר מחדש הופכת את התווים פעמיים ומפיקה פלט משובש. טעות נפוצה של סוכנים היא "לעבד מראש" רשימה שלמה של שורות דרךget_display()ואז לקרוא לו שוב בתוך לולאת הציור. - מחוללי PDF נוטים לכיוון טקסט LTR כברירת מחדל. מסמכים בעברית חייבים כיוון פסקה RTL, וטקסט מעורב עברית-אנגלית צריך תמיכה תקינה באלגוריתם BiDi.
- ל-DOCX (python-docx) יש מלכודת הפוכה מ-PDF: אל תריצו
get_display()על הטקסט, Word מפעיל את אלגוריתם ה-bidi בעצמו ועיבוד מקדים הופך אותו פעמיים. שני הכשלים שמפיקים קובץ Word עברי "שבור" הם (א) הכנסת שורה שלמה מעורבת עברית/אנגלית ל-run אחד המסומן<w:rtl/>(האנגלית קופצת לצד הלא נכון והפיסוק זז) ו-(ב) הגדרתw:ascii/w:szבלבד בלי גופן הסקריפט המורכבw:cs/ גודלw:szCs(הגופן והגודל פשוט לא חלים על העברית). בפסקה מעורבת עברית/אנגלית לא מסמנים rtl על אף run (ה-<w:bidi/>של הפסקה לבדו מסדר אותה, וסימון rtl יהפוך מספרים/לטינית); rtl נשמר רק לפסקאות עבריות טהורות. מגדיריםw:csו-w:szCsעל כל run. - run בלי כיוון מפורש יורש את כיוון הבסיס של הפסקה. אחרי ש-
add_rtl_paragraphמוסיף פסקה עברית, הוספת run נוסף בהמשך (למשל שורת חתימה) בלי להריץ שוב את הפיצול לפי סקריפט עלולה להשאיר את ה-run בלי סימון, הגדירו את כיוונו במפורש במקום להניח שהוא יורש נכון. - סוכנים עלולים לבחור גופנים בלי תמיכה בתווים עבריים (Arial עובד, אבל הרבה גופנים דקורטיביים לטיניים לא). תמיד תוודאו שהגופן כולל את טווח ה-Unicode העברי (U+0590-U+05FF).
- פורמט התאריך בעברית הוא DD/MM/YYYY בהקשר חילוני, ותאריכים עבריים (ט"ו באדר תשפ"ו למשל) למסמכים דתיים/מסורתיים. סוכנים עלולים ללכת ל-MM/DD/YYYY כברירת מחדל.
- מסמכים משפטיים בישראל דורשים עיצוב מסוים: לא משתמשים בניקוד בעברית עסקית/משפטית רגילה. סוכנים עלולים להוסיף ניקוד כי הם חושבים שזה משפר את הבהירות, אבל בפועל זה נראה לא מקצועי במסמכים רשמיים.
פתרון בעיות
שגיאה: "תווים עבריים מוצגים כריבועים או סימני שאלה"
סיבה: גופן עברי לא רשום או לא קיים במערכת פתרון: תורידו גופן TTF עברי (Heebo מ-Google Fonts למשל), רשמו אותו עם pdfmetrics.registerFont() ל-reportlab, או התקינו אותו כגופן מערכת ל-WeasyPrint.
שגיאה: "הטקסט מוצג משמאל לימין במקום מימין לשמאל"
סיבה: חסר סידור bidi או הגדרת כיוון RTL פתרון: ב-reportlab, תפעילו get_display() מ-python-bidi. ב-python-docx, תבנו פסקאות עם העוזר add_rtl_paragraph משלב 5 (מגדיר <w:bidi/> על הפסקה ו-<w:rtl/> על ה-runs העבריים). ב-WeasyPrint, תוודאו dir="rtl" על אלמנט ה-HTML.
שגיאה: "מספרים וסימני פיסוק במיקום שגוי"
סיבה: אלגוריתם הטקסט הדו-כיווני לא מטפל נכון בתוכן מעורב עברית/מספרים פתרון: ב-reportlab, מעבירים את כל המחרוזת הלוגית דרך get_display() בקריאה אחת (ראו "שורות מעורבות עברית / לטינית / ספרות"). בכלים מבוססי HTML (WeasyPrint), תוודאו unicode-bidi: isolate על רכיבי span מוטבעים ב-LTR. ב-DOCX/python-docx עושים את ההפך מתיקון ה-PDF: לעולם לא קוראים ל-get_display() (Word מסדר בעצמו). מגדירים <w:bidi/> על הפסקה ומגדירים גופן w:cs וגודל w:szCs על כל run. בפסקה מעורבת לא מסמנים rtl על אף run; rtl רק על runs עבריים של פסקה ללא לטינית (ראו add_rtl_paragraph בשלב 5).
שגיאה: "קובץ Word עברי מציג אנגלית בצד הלא נכון, או שהגופן/הגודל מתעלמים"
סיבה: כל השורה המעורבת ב-run אחד המסומן <w:rtl/> (האנגלית קופצת), או שה-runs מגדירים רק w:ascii/w:sz ולא את w:cs/w:szCs של הסקריפט המורכב (העברית מתעלמת מהגופן/הגודל). בדיקת נוכחות פשוטה של <w:rtl/> עוברת גם על קובץ שעדיין מרונדר שבור, אז תבדקו את מבנה ה-runs ולא רק את הדגל. פתרון: השתמשו בעוזר add_rtl_paragraph משלב 5: פיצול runs לפי סקריפט, w:cs + w:szCs על כל run, ו-rtl רק על runs עבריים של פסקה ללא לטינית (בפסקה מעורבת אף run לא מסומן).
Related skills
FAQ
Is Hebrew Document Generator safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.