using IronPdf;
// Disable local disk access or cross-origin requests
Installation.EnableWebSecurity = true;
// Instantiate Renderer
var renderer = new ChromePdfRenderer();
// Create a PDF from a HTML string using C#
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
// Export to a file or Stream
pdf.SaveAs("output.pdf");
// Advanced Example with HTML Assets
// Load external html assets: Images, CSS and JavaScript.
// An optional BasePath 'C:\site\assets\' is set as the file location to load assets from
var myAdvancedPdf = renderer.RenderHtmlAsPdf("<img src='icons/iron.png'>", @"C:\site\assets\");
myAdvancedPdf.SaveAs("html-with-assets.pdf");
iText'ten IronPDF'e geçmek, .NET PDF iş akışınızı bir programlama API'sinden — kodda Paragraph, Table ve Cell nesneleri oluşturmaktan — CSS3 ve JavaScript desteği ile HTML-öncelikli bir yaklaşıma taşır. Bu kılavuz, AGPL maruziyetini kaldıran ve ayrı bir pdfHTML eklentisi ihtiyacını ortadan kaldıran adım adım bir geçiş yolu sağlar.
Neden iText'ten IronPDF'e Geçilmeli
AGPL Lisans Hususu
iText (iText Group / Apryse Group NV tarafından sahip olunmaktadır), ticari ekiplerin önceden değerlendirmesi gereken lisanslama özelliklerine sahiptir:
AGPL Viral Lisans: iText'i açık kaynak lisansı altında bir web uygulamasında kullanırsanız, AGPL genellikle uygulamanızın kaynağını ağ üzerinden onunla etkileşime giren herkese yayınlamanız gerektiği şeklinde yorumlanır. Kapalı kaynaklı ticari yazılımlar için bu genellikle ticari bir lisans satın alınması anlamına gelir.
Sadece Abonelik Üzerinden Ticari Lisanslama: iText'in ticari lisansı, PDF işleme hacmine dayalı yıllık abonelik olarak satılmaktadır; süresiz bir seçenek kamuya açık fiyatlandırma sayfasında reklamı yapılmamaktadır (özel OEM şartları sadece teklif üzerinedir).
pdfHTML Eklenti Maliyeti: HTML'den PDF'e dönüşüm, ayrıyeten satılan itext.pdfhtml eklentisini (şu anda 6.3.2) gerektirir, ve çekirdek itext paketinin (şu anda 9.6.0) üzerine satılır.
Karmaşık Lisans Denetimleri: Kurumsal dağıtımlar, lisans karmaşıklığı ve denetim riskiyle karşı karşıya kalır.
Programlanabilir-Öncelikli API: Temel iText, kodda Paragraph, Table ve Cell nesnelerini birleştirerek PDF'leri oluşturur; pdfHTML size HTML ile başlamanıza olanak tanır.
Tanınan CSS Alt Kümesi, Render Zamanında JS Yok: pdfHTML, tanımlı bir CSS alt kümesini destekler ve HTML'yi PDF'ye dönüştürürken JavaScript çalıştırmaz (PDF düzeyinde JS form eylemleri ayrı, desteklenen bir özelliktir).
iText vs IronPDF Karşılaştırması
Özellik
iText 9 / iTextSharp 5.x
IronPDF
Lisans
AGPL (viral) veya ticari yıllık abonelik
Ticari, süresiz seçenek
HTML'den PDF'ye
Ayrı itext.pdfhtml eklentisi (6.3.2)
Dahili Chromium renderleyici
CSS Desteği
pdfHTML'de tanımlı CSS alt kümesi
Tam CSS3, Flexbox, Grid
Render zamanında JavaScript
pdfHTML, HTML→PDF sırasında JS çalıştırmaz
Render zamanında tam JS çalıştırma
API Paradigması
Programatik (Paragraf, Tablo, Hücre)
CSS ile HTML-öncelikli
Öğrenme Eğrisi
Dik (PDF koordinat sistemi)
Web geliştirici dostu
Açık Kaynak Riski
Web uygulamalarını AGPL altında açık kaynak yapmak zorunluluğu
Viral gereklilikler yok
Fiyatlandırma Modeli
Yıllık abonelik (hacme dayalı) veya özel OEM
Süresiz veya abonelik
Modern .NET üzerinde standardize olan ekipler için, IronPDF'nin HTML-odaklı yaklaşımı, ekibin zaten sahip olduğu web geliştirme becerilerine dayanır.
Göç Karmaşıklığı Değerlendirmesi
Özelliklere Göre Tahmini Çaba
Özellik
Göç Karmaşıklığı
HTML'den PDF'ye
Çok Düşük
PDF'leri birleştir
Düşük
Metin ve Görseller
Düşük
Tablolar
Orta
Başlıklar/Altbilgiler
Orta
Güvenlik/Şifreleme
Düşük
Paradigma Değişimi
Bu iText geçişindeki temel değişim, programatik PDF oluşturmadan HTML-öncelikli renderlamaya geçiştir:
Bu paradigma değişimi özgürleştiricidir: iText'in nesne modelini öğrenmek yerine, web geliştiricilerin zaten sahip olduğu HTML ve CSS becerilerini kullanırsınız.
// NuGet: Install-Package itext7
using iText.Html2pdf;
using System.IO;
class Program
{
static void Main()
{
string html = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>";
string outputPath = "output.pdf";
using (FileStream fs = new FileStream(outputPath, FileMode.Create))
{
HtmlConverter.ConvertToPdf(html, fs);
}
}
}
// NuGet: Install-Package itext7
using iText.Html2pdf;
using System.IO;
class Program
{
static void Main()
{
string html = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>";
string outputPath = "output.pdf";
using (FileStream fs = new FileStream(outputPath, FileMode.Create))
{
HtmlConverter.ConvertToPdf(html, fs);
}
}
}
Imports iText.Html2pdf
Imports System.IO
Class Program
Shared Sub Main()
Dim html As String = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
Dim outputPath As String = "output.pdf"
Using fs As FileStream = New FileStream(outputPath, FileMode.Create)
HtmlConverter.ConvertToPdf(html, fs)
End Using
End Sub
End Class
$vbLabelText $csharpLabel
Sonrası (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
string html = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
string html = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
Imports IronPdf
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim html As String = "<h1>Hello World</h1><p>This is a PDF from HTML.</p>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
End Sub
End Class
$vbLabelText $csharpLabel
iText yaklaşımı, ayrı itext.pdfhtml paketini (pdfHTML eklentisi, şu anda 6.3.2, itext 9.6.0'ın üzerine satılır) gerektirir, bir FileStream oluşturur ve her şeyi uygun şekilde atmak için using ifadelerine sarar. HtmlConverter.ConvertToPdf() yöntemi, doğrudan akışa yazar.
IronPDF'ün yaklaşımı daha temizdir: bir ChromePdfRenderer oluşturun, HTML dizginizle RenderHtmlAsPdf() çağırın ve oluşan PdfDocument üzerinde SaveAs() çağırın. Ayrı paketler yok, akış yönetimi yok ve Chromium render motoru üstün CSS3 ve JavaScript desteği sağlar. HTML'den PDF'ye belgelemeyi ek işleme seçenekleri için inceleyin.
Örnek 2: Birden Fazla PDF Birleştir
Before (iText):
// NuGet: Install-Package itext7
using iText.Kernel.Pdf;
using iText.Kernel.Utils;
using System.IO;
class Program
{
static void Main()
{
string outputPath = "merged.pdf";
string[] inputFiles = { "document1.pdf", "document2.pdf", "document3.pdf" };
using (PdfWriter writer = new PdfWriter(outputPath))
using (PdfDocument pdfDoc = new PdfDocument(writer))
{
PdfMerger merger = new PdfMerger(pdfDoc);
foreach (string file in inputFiles)
{
using (PdfDocument sourcePdf = new PdfDocument(new PdfReader(file)))
{
merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages());
}
}
}
}
}
// NuGet: Install-Package itext7
using iText.Kernel.Pdf;
using iText.Kernel.Utils;
using System.IO;
class Program
{
static void Main()
{
string outputPath = "merged.pdf";
string[] inputFiles = { "document1.pdf", "document2.pdf", "document3.pdf" };
using (PdfWriter writer = new PdfWriter(outputPath))
using (PdfDocument pdfDoc = new PdfDocument(writer))
{
PdfMerger merger = new PdfMerger(pdfDoc);
foreach (string file in inputFiles)
{
using (PdfDocument sourcePdf = new PdfDocument(new PdfReader(file)))
{
merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages());
}
}
}
}
}
Imports iText.Kernel.Pdf
Imports iText.Kernel.Utils
Imports System.IO
Class Program
Shared Sub Main()
Dim outputPath As String = "merged.pdf"
Dim inputFiles As String() = {"document1.pdf", "document2.pdf", "document3.pdf"}
Using writer As New PdfWriter(outputPath)
Using pdfDoc As New PdfDocument(writer)
Dim merger As New PdfMerger(pdfDoc)
For Each file As String In inputFiles
Using sourcePdf As New PdfDocument(New PdfReader(file))
merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages())
End Using
Next
End Using
End Using
End Sub
End Class
$vbLabelText $csharpLabel
Sonrası (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System.Collections.Generic;
class Program
{
static void Main()
{
var pdfDocuments = new List<PdfDocument>
{
PdfDocument.FromFile("document1.pdf"),
PdfDocument.FromFile("document2.pdf"),
PdfDocument.FromFile("document3.pdf")
};
var merged = PdfDocument.Merge(pdfDocuments);
merged.SaveAs("merged.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System.Collections.Generic;
class Program
{
static void Main()
{
var pdfDocuments = new List<PdfDocument>
{
PdfDocument.FromFile("document1.pdf"),
PdfDocument.FromFile("document2.pdf"),
PdfDocument.FromFile("document3.pdf")
};
var merged = PdfDocument.Merge(pdfDocuments);
merged.SaveAs("merged.pdf");
}
}
Imports IronPdf
Imports System.Collections.Generic
Class Program
Shared Sub Main()
Dim pdfDocuments As New List(Of PdfDocument) From {
PdfDocument.FromFile("document1.pdf"),
PdfDocument.FromFile("document2.pdf"),
PdfDocument.FromFile("document3.pdf")
}
Dim merged = PdfDocument.Merge(pdfDocuments)
merged.SaveAs("merged.pdf")
End Sub
End Class
$vbLabelText $csharpLabel
iText birleştirme işlemi, önemli miktarda şablon gerektirir: Çıktı için bir PdfWriter oluşturun, bunu bir PdfDocument içine sarın, bir PdfMerger oluşturun, sonra her PdfDocument ve PdfReader için girdiler arasında dolanan iç içe using ifadeleri ile kaynak dosyaları döngüye alın. Sayfa aralıklarını da merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages()) ile belirtmelisiniz.
IronPDF bunu üç adımda azaltır: PdfDocument.FromFile() ile belgeleri yükleyin, listeyle statik PdfDocument.Merge() metodunu çağırın ve kaydedin. Tüm birleştirme işlemi okunabilir ve bakımı yapılabilir hale gelir. Daha fazla bilgi edinin PDF birleştirme ve bölme.
Örnek 3: Metin ve Görsellerle PDF Oluşturma
Before (iText):
// NuGet: Install-Package itext7
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
using iText.IO.Image;
class Program
{
static void Main()
{
string outputPath = "document.pdf";
using (PdfWriter writer = new PdfWriter(outputPath))
using (PdfDocument pdf = new PdfDocument(writer))
using (Document document = new Document(pdf))
{
document.Add(new Paragraph("Sample PDF Document"));
document.Add(new Paragraph("This document contains text and an image."));
Image img = new Image(ImageDataFactory.Create("image.jpg"));
img.SetWidth(200);
document.Add(img);
}
}
}
// NuGet: Install-Package itext7
using iText.Kernel.Pdf;
using iText.Layout;
using iText.Layout.Element;
using iText.IO.Image;
class Program
{
static void Main()
{
string outputPath = "document.pdf";
using (PdfWriter writer = new PdfWriter(outputPath))
using (PdfDocument pdf = new PdfDocument(writer))
using (Document document = new Document(pdf))
{
document.Add(new Paragraph("Sample PDF Document"));
document.Add(new Paragraph("This document contains text and an image."));
Image img = new Image(ImageDataFactory.Create("image.jpg"));
img.SetWidth(200);
document.Add(img);
}
}
}
Imports iText.Kernel.Pdf
Imports iText.Layout
Imports iText.Layout.Element
Imports iText.IO.Image
Class Program
Shared Sub Main()
Dim outputPath As String = "document.pdf"
Using writer As New PdfWriter(outputPath),
pdf As New PdfDocument(writer),
document As New Document(pdf)
document.Add(New Paragraph("Sample PDF Document"))
document.Add(New Paragraph("This document contains text and an image."))
Dim img As New Image(ImageDataFactory.Create("image.jpg"))
img.SetWidth(200)
document.Add(img)
End Using
End Sub
End Class
$vbLabelText $csharpLabel
Sonrası (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
string html = @"
<h1>Sample PDF Document</h1>
<p>This document contains text and an image.</p>
<img src='image.jpg' width='200' />";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("document.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
string html = @"
<h1>Sample PDF Document</h1>
<p>This document contains text and an image.</p>
<img src='image.jpg' width='200' />";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("document.pdf");
}
}
Imports IronPdf
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim html As String = "
<h1>Sample PDF Document</h1>
<p>This document contains text and an image.</p>
<img src='image.jpg' width='200' />"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("document.pdf")
End Sub
End Class
$vbLabelText $csharpLabel
Bu örnek paradigma değişimini en açık şekilde gösterir. iText şunları gerektirir:
Üç kez iç içe using ifadeleri (PdfWriter, PdfDocument, Document)
Her metin elementi için new Paragraph() ile Paragraph nesneleri oluşturma
Görüntüleri yüklemek için ImageDataFactory.Create() kullanma
Image nesnesi oluşturma ve ayrı ayrı SetWidth() çağırma
Her eleman için document.Add() çağırma
IronPDF standart HTML kullanır: başlıklar için , paragraflar için ve width özelliği ile görüntüler için . Web geliştiricileri var olan becerilerini hemen kullanabilir ve tasarımcılar halihazırda bildikleri CSS'i kullanarak belgeleri tasarlayabilir.
Kritik Taşıma Notları
Paradigma Değişimi: Programatikten HTML-Öncelikli
Bu iText geçişindeki en önemli değişim kavramsaldır. iText, PDF'leri programatik olarak oluşturur:
// iText approach
document.Add(new Paragraph("Title")
.SetTextAlignment(TextAlignment.CENTER)
.SetFontSize(24)
.SetBold());
var table = new Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth();
table.AddHeaderCell(new Cell().Add(new Paragraph("ID")));
table.AddHeaderCell(new Cell().Add(new Paragraph("Name")));
// ... many more lines
// iText approach
document.Add(new Paragraph("Title")
.SetTextAlignment(TextAlignment.CENTER)
.SetFontSize(24)
.SetBold());
var table = new Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth();
table.AddHeaderCell(new Cell().Add(new Paragraph("ID")));
table.AddHeaderCell(new Cell().Add(new Paragraph("Name")));
// ... many more lines
' iText approach
document.Add(New Paragraph("Title") _
.SetTextAlignment(TextAlignment.CENTER) _
.SetFontSize(24) _
.SetBold())
Dim table = New Table(UnitValue.CreatePercentArray(3)).UseAllAvailableWidth()
table.AddHeaderCell(New Cell().Add(New Paragraph("ID")))
table.AddHeaderCell(New Cell().Add(New Paragraph("Name")))
' ... many more lines
Imports IronPdf
Dim html As String = "
<style>
h1 { text-align: center; font-size: 24px; font-weight: bold; }
table { width: 100%; border-collapse: collapse; }
th { background-color: #4CAF50; color: white; padding: 8px; }
</style>
<h1>Title</h1>
<table>
<tr><th>ID</th><th>Name</th></tr>
</table>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
$vbLabelText $csharpLabel
AGPL Maruziyeti Kaldırıldı
iText'in AGPL terimlerine göre, iText kullanan bir web uygulaması genellikle ağ üzerinden onunla etkileşime giren herkese kaynağını açması beklenir; ticari lisans alternatif bir yoldur. IronPDF'nin ticari lisansı, viral lisanslama gereklilikleri olmaksızın tescilli yazılımda dağıtıma izin verir.
pdfHTML Eklentisi Gerekli Değil
iText, çekirdek itext paketinin üzerinde satılan HTML'den PDF'e dönüşüm için ayrı itext.pdfhtml eklentisini (şu anda 6.3.2) gerektirir. IronPDF, temel pakette Chromium tabanlı HTML renderı içerir.
Yöntem Değiştirme Kalıpları
iText Kalıbı
IronPDF Değiştirme
SetTextAlignment(TextAlignment.CENTER)
CSS text-align: center
SetFontSize(24)
CSS font-size: 24px
SetBold()
CSS font-weight: bold
new Table(3)
HTML
AddHeaderCell(new Cell().Add(new Paragraph()))
HTML
AddCell(new Cell().Add(new Paragraph()))
HTML
Hata Ayıklama
Sorun 1: PdfWriter/Document Kalıbı
Probleme: Kod, PdfWriter → PdfDocument → Document iç içe geçme modelini kullanır.
Çözüm:ChromePdfRenderer ile değiştirin:
// Delete this iText pattern:
// using (var writer = new PdfWriter(outputPath))
// using (var pdfDoc = new PdfDocument(writer))
// using (var document = new Document(pdfDoc))
// Replace with:
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs(outputPath);
// Delete this iText pattern:
// using (var writer = new PdfWriter(outputPath))
// using (var pdfDoc = new PdfDocument(writer))
// using (var document = new Document(pdfDoc))
// Replace with:
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs(outputPath);
Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs(outputPath)
$vbLabelText $csharpLabel
Sorun 2: HtmlConverter Bulunamadı
Probleme: Kod, ayrı olarak satın alınan itext.pdfhtml eklentisinde bulunan iText.Html2pdf.HtmlConverter kullanıyor.
Çözüm: IronPDF'nin yerleşik HTML işleme özelliğini kullanın:
// iText (requires pdfHTML add-on)
HtmlConverter.ConvertToPdf(html, fileStream);
//IronPDF(built-in)
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs(outputPath);
// iText (requires pdfHTML add-on)
HtmlConverter.ConvertToPdf(html, fileStream);
//IronPDF(built-in)
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs(outputPath);
' iText (requires pdfHTML add-on)
HtmlConverter.ConvertToPdf(html, fileStream)
' IronPDF(built-in)
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs(outputPath)
$vbLabelText $csharpLabel
Sorun 3: PdfMerger Karmaşıklığı
Probleme: iText'ün PdfMerger, iç içe okuyucular ve sayfa aralığı belirtimi gerektirir.
// iText merger pattern (delete this)
// using (PdfDocument pdfDoc = new PdfDocument(writer))
// {
// PdfMerger merger = new PdfMerger(pdfDoc);
// foreach (string file in inputFiles)
// {
// using (PdfDocument sourcePdf = new PdfDocument(new PdfReader(file)))
// {
// merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages());
// }
// }
// }
//IronPDF(simple)
var merged = PdfDocument.Merge(pdfDocuments);
merged.SaveAs("merged.pdf");
// iText merger pattern (delete this)
// using (PdfDocument pdfDoc = new PdfDocument(writer))
// {
// PdfMerger merger = new PdfMerger(pdfDoc);
// foreach (string file in inputFiles)
// {
// using (PdfDocument sourcePdf = new PdfDocument(new PdfReader(file)))
// {
// merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages());
// }
// }
// }
//IronPDF(simple)
var merged = PdfDocument.Merge(pdfDocuments);
merged.SaveAs("merged.pdf");
Imports System.Collections.Generic
' iText merger pattern (delete this)
' Using pdfDoc As New PdfDocument(writer)
' Dim merger As New PdfMerger(pdfDoc)
' For Each file As String In inputFiles
' Using sourcePdf As New PdfDocument(New PdfReader(file))
' merger.Merge(sourcePdf, 1, sourcePdf.GetNumberOfPages())
' End Using
' Next
' End Using
' IronPDF(simple)
Dim merged = PdfDocument.Merge(pdfDocuments)
merged.SaveAs("merged.pdf")
$vbLabelText $csharpLabel
Geçiş Kontrol Listesi
Öncesi-Geçiş
Kod tabanındaki tüm iText API çağrılarını envanterleyin
Programatik PDF oluşturma kalıplarını (Paragraph, Table, Cell) belirleyin
HtmlConverter kullanımını belgelendirin (pdfHTML eklentisi)
AGPL uyumluluk riskini değerlendirin
-IronPDF lisans anahtarını edinin
Tüm PDF oluşturma yollarını test edin
Görsel çıktının beklentilere uygun olduğunu doğrulayın
Karmaşık HTML/CSS içeriği ile test edin
Performansı karşılaştırın
Geçiş Sonrası
iText lisans dosyalarını ve referanslarını kaldırın
Lütfen dikkate alıniText, ilgili sahibinin tescilli markasıdır. Bu site, iText Group ile ilişkili, onaylanmış veya desteklenmiş değildir. Tüm ürün adları, logolar ve markalar, ilgili sahiplerinin mülkiyetindedir. Karşılaştırmalar, yalnızca bilgilendirme amaçlıdır ve yazı sırasında halka açık bilgilerle alakalı olarak yansıtılmaktadır.
Curtis Chau, Bilgisayar Bilimleri alanında Lisans Derecesine (Carleton Üniversitesi) sahip ve Node.js, TypeScript, JavaScript ve React konularında uzmanlaşmış ön uç geliştirmeyle ilgileniyor. Sezgisel ve estetik açıdan hoş kullanıcı arayüzleri oluşturma tutkunu, Curtis modern çerçevelerle çalışmayı ve iyi yapı...